From 30adf9a10cd44c7125897ddb4035c43b939893a4 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Mon, 26 Jun 2023 17:34:14 +0200 Subject: [PATCH 001/754] fix: nostrikethrough prop draft solution --- .../HTMLRenderers/EditedRenderer.js | 22 ++++++----- .../applyStrikethrough/index.js | 5 --- .../applyStrikethrough/index.native.js | 8 ---- src/components/OfflineWithFeedback.js | 2 +- src/pages/home/report/ReportActionItem.js | 1 + .../home/report/ReportActionItemFragment.js | 39 +++++++++++-------- src/styles/styles.js | 1 + 7 files changed, 37 insertions(+), 41 deletions(-) delete mode 100644 src/components/HTMLEngineProvider/applyStrikethrough/index.js delete mode 100644 src/components/HTMLEngineProvider/applyStrikethrough/index.native.js diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js index d91510c3ec6a..4ea53287f756 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js @@ -17,21 +17,23 @@ function EditedRenderer(props) { const defaultRendererProps = _.omit(props, ['TDefaultRenderer', 'style', 'tnode']); const isPendingDelete = Boolean(props.tnode.attributes.deleted !== undefined); return ( - - {/* Native devices do not support margin between nested text */} + {' '} - {props.translate('reportActionCompose.edited')} + + {/* Native devices do not support margin between nested text */} + {props.translate('reportActionCompose.edited')} + ); } diff --git a/src/components/HTMLEngineProvider/applyStrikethrough/index.js b/src/components/HTMLEngineProvider/applyStrikethrough/index.js deleted file mode 100644 index b754a37dbde9..000000000000 --- a/src/components/HTMLEngineProvider/applyStrikethrough/index.js +++ /dev/null @@ -1,5 +0,0 @@ -function applyStrikethrough(html) { - return html; -} - -export default applyStrikethrough; diff --git a/src/components/HTMLEngineProvider/applyStrikethrough/index.native.js b/src/components/HTMLEngineProvider/applyStrikethrough/index.native.js deleted file mode 100644 index 266e4eb20f62..000000000000 --- a/src/components/HTMLEngineProvider/applyStrikethrough/index.native.js +++ /dev/null @@ -1,8 +0,0 @@ -function applyStrikethrough(html, isPendingDelete) { - if (isPendingDelete) { - return `${html}`; - } - return html; -} - -export default applyStrikethrough; diff --git a/src/components/OfflineWithFeedback.js b/src/components/OfflineWithFeedback.js index 5cb5fc57dc24..120c0d092453 100644 --- a/src/components/OfflineWithFeedback.js +++ b/src/components/OfflineWithFeedback.js @@ -97,7 +97,7 @@ function OfflineWithFeedback(props) { const isUpdateOrDeleteError = hasErrors && (props.pendingAction === 'delete' || props.pendingAction === 'update'); const isAddError = hasErrors && props.pendingAction === 'add'; const needsOpacity = (isOfflinePendingAction && !isUpdateOrDeleteError) || isAddError; - const needsStrikeThrough = props.network.isOffline && props.pendingAction === 'delete'; + const needsStrikeThrough = !props.noStrikeThrough && props.network.isOffline && props.pendingAction === 'delete'; const hideChildren = props.shouldHideOnDelete && !props.network.isOffline && props.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && !hasErrors; let children = props.children; diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 24be64dc6468..442086c7c5da 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -480,6 +480,7 @@ function ReportActionItem(props) { errors={props.action.errors} errorRowStyles={[styles.ml10, styles.mr2]} needsOffscreenAlphaCompositing={ReportActionsUtils.isMoneyRequestAction(props.action)} + noStrikeThrough > {isWhisper && ( diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index 65558110ba45..edf8955021d1 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -16,7 +16,6 @@ import compose from '../../../libs/compose'; import convertToLTR from '../../../libs/convertToLTR'; import {withNetwork} from '../../../components/OnyxProvider'; import CONST from '../../../CONST'; -import applyStrikethrough from '../../../components/HTMLEngineProvider/applyStrikethrough'; import editedLabelStyles from '../../../styles/editedLabelStyles'; import UserDetailsTooltip from '../../../components/UserDetailsTooltip'; @@ -95,6 +94,7 @@ function ReportActionItemFragment(props) { ); } const {html, text} = props.fragment; + const isPendingDelete = props.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && props.network.isOffline; // Threaded messages display "[Deleted message]" instead of being hidden altogether. // While offline we display the previous message with a strikethrough style. Once online we want to @@ -111,34 +111,39 @@ function ReportActionItemFragment(props) { // Only render HTML if we have html in the fragment if (!differByLineBreaksOnly) { - const isPendingDelete = props.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && props.network.isOffline; const editedTag = props.fragment.isEdited ? `` : ''; - const htmlContent = applyStrikethrough(html + editedTag, isPendingDelete); + const htmlContent = isPendingDelete ? `${html}` : html - return ${htmlContent}` : `${htmlContent}`} />; + const htmlWithTag = editedTag ? `${htmlContent}${editedTag}` : htmlContent; + + return ${htmlWithTag}` : `${htmlWithTag}`} />; } const containsOnlyEmojis = EmojiUtils.containsOnlyEmojis(text); return ( - - {convertToLTR(text)} + + + {convertToLTR(text)} + {Boolean(props.fragment.isEdited) && ( - + <> {' '} - {props.translate('reportActionCompose.edited')} - + + {props.translate('reportActionCompose.edited')} + + )} ); diff --git a/src/styles/styles.js b/src/styles/styles.js index 74066af5e20a..7c4944476195 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -77,6 +77,7 @@ const webViewStyles = { del: { textDecorationLine: 'line-through', textDecorationStyle: 'solid', + flex: 1, }, strong: { From 8548ac7ecbb6d1aebb3c97139f9c64ebae83707e Mon Sep 17 00:00:00 2001 From: Puneet Date: Tue, 4 Jul 2023 05:02:02 +0530 Subject: [PATCH 002/754] add new translation keys --- src/languages/en.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/languages/en.js b/src/languages/en.js index f6989d32f35a..e5fcffe78e3d 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -391,6 +391,7 @@ export default { uploadPhoto: 'Upload photo', removePhoto: 'Remove photo', editImage: 'Edit photo', + viewPhoto: 'View photo', imageUploadFailed: 'Image upload failed', deleteWorkspaceError: 'Sorry, there was an unexpected problem deleting your workspace avatar.', sizeExceeded: ({maxUploadSizeInMB}) => `The selected image exceeds the maximum upload size of ${maxUploadSizeInMB}MB.`, @@ -411,6 +412,7 @@ export default { online: 'Online', offline: 'Offline', syncing: 'Syncing', + profileAvatar: 'Profile avatar', }, loungeAccessPage: { loungeAccess: 'Lounge access', @@ -1090,6 +1092,7 @@ export default { memberNotFound: 'Member not found. To invite a new member to the workspace, please use the Invite button above.', notAuthorized: `You do not have access to this page. Are you trying to join the workspace? Please reach out to the owner of this workspace so they can add you as a member! Something else? Reach out to ${CONST.EMAIL.CONCIERGE}`, goToRoom: ({roomName}) => `Go to ${roomName} room`, + workspaceAvatar: 'Workspace avatar', }, emptyWorkspace: { title: 'Create a new workspace', From 7e39af512130d011e4ff4fe271df52fb9e421c49 Mon Sep 17 00:00:00 2001 From: Puneet Date: Tue, 4 Jul 2023 05:02:35 +0530 Subject: [PATCH 003/754] Update es.js --- src/languages/es.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/languages/es.js b/src/languages/es.js index f765ce0270df..281c81b3ad59 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -390,6 +390,7 @@ export default { uploadPhoto: 'Subir foto', removePhoto: 'Eliminar foto', editImage: 'Editar foto', + viewPhoto: 'Ver foto', imageUploadFailed: 'Error al cargar la imagen', deleteWorkspaceError: 'Lo sentimos, hubo un problema eliminando el avatar de su espacio de trabajo.', sizeExceeded: ({maxUploadSizeInMB}) => `La imagen supera el tamaño máximo de ${maxUploadSizeInMB}MB.`, @@ -410,6 +411,7 @@ export default { online: 'En línea', offline: 'Desconectado', syncing: 'Sincronizando', + profileAvatar: 'Perfil avatar', }, loungeAccessPage: { loungeAccess: 'Acceso a la sala vip', @@ -1097,6 +1099,7 @@ export default { memberNotFound: 'Miembro no encontrado. Para invitar a un nuevo miembro al espacio de trabajo, por favor, utiliza el botón Invitar que está arriba.', notAuthorized: `No tienes acceso a esta página. ¿Estás tratando de unirte al espacio de trabajo? Comunícate con el propietario de este espacio de trabajo para que pueda agregarte como miembro. ¿Necesitas algo más? Comunícate con ${CONST.EMAIL.CONCIERGE}`, goToRoom: ({roomName}) => `Ir a la sala ${roomName}`, + workspaceAvatar: 'Espacio de trabajo avatar', }, emptyWorkspace: { title: 'Crear un nuevo espacio de trabajo', From 08a930233f9d74969ea20e5ef464bbebab1ca453 Mon Sep 17 00:00:00 2001 From: Puneet Date: Tue, 4 Jul 2023 05:08:42 +0530 Subject: [PATCH 004/754] add avatar preview option --- src/components/AvatarWithImagePicker.js | 131 ++++++++++++++---------- 1 file changed, 79 insertions(+), 52 deletions(-) diff --git a/src/components/AvatarWithImagePicker.js b/src/components/AvatarWithImagePicker.js index 45e75aaeec57..74a28c3821c9 100644 --- a/src/components/AvatarWithImagePicker.js +++ b/src/components/AvatarWithImagePicker.js @@ -22,6 +22,7 @@ import stylePropTypes from '../styles/stylePropTypes'; import * as FileUtils from '../libs/fileDownload/FileUtils'; import getImageResolution from '../libs/fileDownload/getImageResolution'; import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; +import AttachmentModal from './AttachmentModal'; const propTypes = { /** Avatar source to display */ @@ -78,6 +79,15 @@ const propTypes = { // eslint-disable-next-line react/forbid-prop-types errors: PropTypes.object, + /** Title for avatar preview modal */ + headerTitle: PropTypes.string, + + /** Avatar source for avatar preview modal */ + previewSource: PropTypes.string, + + /** File name of the avatar */ + originalFileName: PropTypes.string, + ...withLocalizePropTypes, }; @@ -97,6 +107,9 @@ const defaultProps = { onErrorClose: () => {}, pendingAction: null, errors: null, + headerTitle: '', + previewSource: '', + originalFileName: '', }; class AvatarWithImagePicker extends React.Component { @@ -241,9 +254,10 @@ class AvatarWithImagePicker extends React.Component { * Create menu items list for avatar menu * * @param {Function} openPicker + * @param {Function} show * @returns {Array} */ - createMenuItems(openPicker) { + createMenuItems(openPicker, show) { const menuItems = [ { icon: Expensicons.Upload, @@ -265,6 +279,11 @@ class AvatarWithImagePicker extends React.Component { this.props.onImageRemoved(); }, }); + menuItems.unshift({ + icon: Expensicons.Eye, + text: this.props.translate('avatarWithImagePicker.viewPhoto'), + onSelected: () => show(), + }); } return menuItems; } @@ -275,62 +294,70 @@ class AvatarWithImagePicker extends React.Component { return ( - this.setState({isMenuVisible: true})} - accessibilityRole="button" - accessibilityLabel={this.props.translate('avatarWithImagePicker.editImage')} + - - ( + this.setState({isMenuVisible: true})} + accessibilityRole="button" + accessibilityLabel={this.props.translate('avatarWithImagePicker.editImage')} > - - - {this.props.source ? ( - - ) : ( - - )} - - - - - - {({openPicker}) => ( - <> + + - - + + {this.props.source ? ( + + ) : ( + + )} - this.setState({isMenuVisible: false})} - onItemSelected={() => this.setState({isMenuVisible: false})} - menuItems={this.createMenuItems(openPicker)} - anchorPosition={this.props.anchorPosition} - anchorAlignment={this.props.anchorAlignment} - /> - - )} - - - + + + + {({openPicker}) => ( + <> + + + + + + this.setState({isMenuVisible: false})} + onItemSelected={() => this.setState({isMenuVisible: false})} + menuItems={this.createMenuItems(openPicker, show)} + anchorPosition={this.props.anchorPosition} + anchorAlignment={this.props.anchorAlignment} + /> + + )} + + + + )} + Date: Tue, 4 Jul 2023 05:10:09 +0530 Subject: [PATCH 005/754] make profile avatar viewable --- src/pages/settings/Profile/ProfilePage.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js index 07db3d0cdffb..9a1aefb4e2c3 100755 --- a/src/pages/settings/Profile/ProfilePage.js +++ b/src/pages/settings/Profile/ProfilePage.js @@ -60,6 +60,8 @@ function ProfilePage(props) { }; const currentUserDetails = props.currentUserPersonalDetails || {}; const contactMethodBrickRoadIndicator = UserUtils.getLoginListBrickRoadIndicator(props.loginList); + const avatarURL = lodashGet(currentUserDetails, 'avatar', ''); + const accountID = lodashGet(currentUserDetails, 'accountID', ''); const profileSettingsOptions = [ { @@ -98,7 +100,7 @@ function ProfilePage(props) { {_.map(profileSettingsOptions, (detail, index) => ( From 5643d7451818448c88b4b9af8d555d8fd46d74f8 Mon Sep 17 00:00:00 2001 From: Puneet Date: Tue, 4 Jul 2023 05:12:43 +0530 Subject: [PATCH 006/754] add orginalFileName --- src/libs/actions/Policy.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index bad48978b0e1..ea6d1cb38942 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -426,6 +426,7 @@ function updateWorkspaceAvatar(policyID, file) { key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { avatar: file.uri, + originalFileName: file.name, errorFields: { avatar: null, }, From d2ca2249e1d8c363304b7cec8efdc96935567253 Mon Sep 17 00:00:00 2001 From: Puneet Date: Tue, 4 Jul 2023 05:13:53 +0530 Subject: [PATCH 007/754] add avatar preview feature at workpace page --- src/pages/workspace/WorkspaceSettingsPage.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pages/workspace/WorkspaceSettingsPage.js b/src/pages/workspace/WorkspaceSettingsPage.js index 6abde456f40b..4aeebfbd54b3 100644 --- a/src/pages/workspace/WorkspaceSettingsPage.js +++ b/src/pages/workspace/WorkspaceSettingsPage.js @@ -20,6 +20,7 @@ import {withNetwork} from '../../components/OnyxProvider'; import OfflineWithFeedback from '../../components/OfflineWithFeedback'; import Form from '../../components/Form'; import * as ReportUtils from '../../libs/ReportUtils'; +import * as UserUtils from '../../libs/UserUtils'; import Avatar from '../../components/Avatar'; import Navigation from '../../libs/Navigation/Navigation'; import ROUTES from '../../ROUTES'; @@ -125,6 +126,9 @@ function WorkspaceSettingsPage(props) { pendingAction={lodashGet(props.policy, 'pendingFields.avatar', null)} errors={lodashGet(props.policy, 'errorFields.avatar', null)} onErrorClose={() => Policy.clearAvatarErrors(props.policy.id)} + previewSource={UserUtils.getFullSizeAvatar(props.policy.avatar, '')} + headerTitle={props.translate('workspace.common.workspaceAvatar')} + originalFileName={props.policy.originalFileName} /> Date: Sat, 22 Jul 2023 10:09:53 +0500 Subject: [PATCH 008/754] 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 009/754] 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: Thu, 27 Jul 2023 10:21:00 +0700 Subject: [PATCH 010/754] 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) { ) : ( )} - -