diff --git a/src/CONST.ts b/src/CONST.ts index 9ab5d56de384..e96a3d4f7f48 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1084,7 +1084,7 @@ const CONST = { DELAYED_SUBMISSION_HELP_URL: 'https://help.expensify.com/articles/expensify-classic/reports/Automatically-submit-employee-reports', ENCRYPTION_AND_SECURITY_HELP_URL: 'https://help.expensify.com/articles/new-expensify/settings/Encryption-and-Data-Security', PLAN_TYPES_AND_PRICING_HELP_URL: 'https://help.expensify.com/articles/new-expensify/billing-and-subscriptions/Plan-types-and-pricing', - MERGE_ACCOUNT_HELP_URL: 'https://help.expensify.com/articles/expensify-classic/settings/Merge-accounts', + MERGE_ACCOUNT_HELP_URL: 'https://help.expensify.com/articles/new-expensify/settings/Merge-Accounts', CONNECT_A_BUSINESS_BANK_ACCOUNT_HELP_URL: 'https://help.expensify.com/articles/new-expensify/expenses-&-payments/Connect-a-Business-Bank-Account', TEST_RECEIPT_URL: `${CLOUDFRONT_URL}/images/fake-receipt__tacotodds.png`, // Use Environment.getEnvironmentURL to get the complete URL with port number diff --git a/src/components/ConfirmationPage.tsx b/src/components/ConfirmationPage.tsx index 80277fcaeb58..d4de46017734 100644 --- a/src/components/ConfirmationPage.tsx +++ b/src/components/ConfirmationPage.tsx @@ -106,7 +106,7 @@ function ConfirmationPage({ )} {heading} - {description} + {description} {cta ? {cta} : null} {(shouldShowSecondaryButton || shouldShowButton) && ( diff --git a/src/languages/en.ts b/src/languages/en.ts index a5ade77ef024..4da305d626c8 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1428,6 +1428,10 @@ const translations = { confirmMerge: 'Are you sure you want to merge accounts?', lossOfUnsubmittedData: `Merging your accounts is irreversible and will result in the loss of any unsubmitted expenses for `, enterMagicCode: `To continue, please enter the magic code sent to `, + errors: { + incorrect2fa: 'Incorrect two-factor authentication code. Please try again.', + fallback: 'Something went wrong. Please try again later.', + }, }, mergeSuccess: { accountsMerged: 'Accounts merged!', diff --git a/src/languages/es.ts b/src/languages/es.ts index 93ad6096918b..87e0f09269ac 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1427,6 +1427,10 @@ const translations = { confirmMerge: '¿Estás seguro de que deseas fusionar cuentas?', lossOfUnsubmittedData: `Fusionar tus cuentas es irreversible y resultará en la pérdida de cualquier gasto no enviado de `, enterMagicCode: `Para continuar, por favor introduce el código mágico enviado a `, + errors: { + incorrect2fa: 'Código de autenticación de dos factores incorrecto. Por favor, inténtalo de nuevo.', + fallback: 'Ha ocurrido un error. Por favor, inténtalo mas tarde.', + }, }, mergeSuccess: { accountsMerged: '¡Cuentas fusionadas!', diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts index 0794bed68693..1fa5705ab3bc 100755 --- a/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts +++ b/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts @@ -52,6 +52,9 @@ const CENTRAL_PANE_TO_RHP_MAPPING: Partial (session = value ?? {}), }); -function requestValidationCodeForAccountMerge(email: string) { +function requestValidationCodeForAccountMerge(email: string, validateCodeResent = false) { const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -21,6 +21,7 @@ function requestValidationCodeForAccountMerge(email: string) { getValidateCodeForAccountMerge: { isLoading: true, validateCodeSent: false, + validateCodeResent: false, errors: null, }, }, @@ -34,7 +35,8 @@ function requestValidationCodeForAccountMerge(email: string) { value: { getValidateCodeForAccountMerge: { isLoading: false, - validateCodeSent: true, + validateCodeSent: !validateCodeResent, + validateCodeResent, errors: null, }, }, @@ -49,6 +51,7 @@ function requestValidationCodeForAccountMerge(email: string) { getValidateCodeForAccountMerge: { isLoading: false, validateCodeSent: false, + validateCodeResent: false, }, }, }, @@ -67,6 +70,7 @@ function clearGetValidateCodeForAccountMerge() { getValidateCodeForAccountMerge: { errors: null, validateCodeSent: false, + validateCodeResent: false, isLoading: false, }, }); diff --git a/src/pages/settings/Security/MergeAccounts/AccountDetailsPage.tsx b/src/pages/settings/Security/MergeAccounts/AccountDetailsPage.tsx index 68f1b271fbcb..0bc337ca71c7 100644 --- a/src/pages/settings/Security/MergeAccounts/AccountDetailsPage.tsx +++ b/src/pages/settings/Security/MergeAccounts/AccountDetailsPage.tsx @@ -1,7 +1,7 @@ -import {useFocusEffect, useRoute} from '@react-navigation/native'; +import {useFocusEffect, useNavigation, useRoute} from '@react-navigation/native'; import {Str} from 'expensify-common'; -import React, {useCallback, useRef, useState} from 'react'; -import {View} from 'react-native'; +import React, {useCallback, useEffect, useRef, useState} from 'react'; +import {InteractionManager, View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import CheckboxWithLabel from '@components/CheckboxWithLabel'; @@ -16,11 +16,10 @@ import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import {addErrorMessage, getLatestErrorMessage} from '@libs/ErrorUtils'; -import {appendCountryCode, getPhoneNumberWithoutSpecialChars} from '@libs/LoginUtils'; +import {getPhoneLogin, validateNumber} from '@libs/LoginUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; -import {parsePhoneNumber} from '@libs/PhoneNumber'; import {isNumericWithSpecialChars} from '@libs/ValidationUtils'; import {clearGetValidateCodeForAccountMerge, requestValidationCodeForAccountMerge} from '@userActions/MergeAccounts'; import CONST from '@src/CONST'; @@ -52,6 +51,7 @@ const getValidateCodeErrorKey = (err: string): ValueOf(null); + const navigation = useNavigation(); const [userEmailOrPhone] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.email, canBeMissing: true}); const [getValidateCodeForAccountMerge] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => account?.getValidateCodeForAccountMerge, canBeMissing: true}); const {params} = useRoute>(); @@ -67,30 +67,38 @@ function AccountDetailsPage() { useFocusEffect( useCallback(() => { - if (!validateCodeSent || !email) { - return; - } + const task = InteractionManager.runAfterInteractions(() => { + if (!validateCodeSent || !email) { + return; + } + + return Navigation.navigate(ROUTES.SETTINGS_MERGE_ACCOUNTS_MAGIC_CODE.getRoute(email)); + }); - return Navigation.navigate(ROUTES.SETTINGS_MERGE_ACCOUNTS_MAGIC_CODE.getRoute(email)); + return () => task.cancel(); }, [validateCodeSent, email]), ); useFocusEffect( useCallback(() => { - if (!errorKey || !email) { - return; - } - return Navigation.navigate(ROUTES.SETTINGS_MERGE_ACCOUNTS_RESULT.getRoute(email, errorKey)); + const task = InteractionManager.runAfterInteractions(() => { + if (!errorKey || !email) { + return; + } + return Navigation.navigate(ROUTES.SETTINGS_MERGE_ACCOUNTS_RESULT.getRoute(email, errorKey)); + }); + + return () => task.cancel(); }, [errorKey, email]), ); - useFocusEffect( - useCallback(() => { - return () => { - clearGetValidateCodeForAccountMerge(); - }; - }, []), - ); + useEffect(() => { + const unsubscribe = navigation.addListener('blur', () => { + clearGetValidateCodeForAccountMerge(); + }); + + return unsubscribe; + }, [navigation]); const validate = (values: FormOnyxValues): Errors => { const errors = {}; @@ -99,11 +107,13 @@ function AccountDetailsPage() { if (!login) { addErrorMessage(errors, INPUT_IDS.PHONE_OR_EMAIL, translate('common.pleaseEnterEmailOrPhoneNumber')); + } else if (login.trim() === userEmailOrPhone) { + addErrorMessage(errors, INPUT_IDS.PHONE_OR_EMAIL, translate('common.error.email')); } else { - const phoneLogin = appendCountryCode(getPhoneNumberWithoutSpecialChars(login)); - const parsedPhoneNumber = parsePhoneNumber(phoneLogin); + const phoneLogin = getPhoneLogin(login); + const validateIfNumber = validateNumber(phoneLogin); - if (!Str.isValidEmail(login) && !parsedPhoneNumber.possible) { + if (!Str.isValidEmail(login) && !validateIfNumber) { if (isNumericWithSpecialChars(login)) { addErrorMessage(errors, INPUT_IDS.PHONE_OR_EMAIL, translate('common.error.phoneNumber')); } else { diff --git a/src/pages/settings/Security/MergeAccounts/AccountValidatePage.tsx b/src/pages/settings/Security/MergeAccounts/AccountValidatePage.tsx index 5e26a5709e55..0df368c1ac4a 100644 --- a/src/pages/settings/Security/MergeAccounts/AccountValidatePage.tsx +++ b/src/pages/settings/Security/MergeAccounts/AccountValidatePage.tsx @@ -1,5 +1,5 @@ -import {useFocusEffect, useRoute} from '@react-navigation/native'; -import React, {useCallback, useRef} from 'react'; +import {useFocusEffect, useNavigation, useRoute} from '@react-navigation/native'; +import React, {useCallback, useEffect, useRef} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; @@ -22,11 +22,12 @@ import { requestValidationCodeForAccountMerge, } from '@userActions/MergeAccounts'; import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -const getMergeErrorKey = (err: string): ValueOf | null => { +const getMergeErrorPage = (err: string): ValueOf | null => { if (err.includes('403')) { return CONST.MERGE_ACCOUNT_RESULTS.TOO_MANY_ATTEMPTS; } @@ -58,8 +59,22 @@ const getMergeErrorKey = (err: string): ValueOf { + if (!err) { + return null; + } + + if (err.includes('Invalid validateCode')) { + return 'mergeAccountsPage.accountValidate.errors.incorrect2fa'; + } + + return 'mergeAccountsPage.accountValidate.errors.fallback'; +}; + function AccountValidatePage() { const validateCodeFormRef = useRef(null); + const navigation = useNavigation(); + const [account] = useOnyx(ONYXKEYS.ACCOUNT, { selector: (data) => ({ mergeWithValidateCode: data?.mergeWithValidateCode, @@ -78,7 +93,7 @@ function AccountValidatePage() { const isAccountMerged = mergeWithValidateCode?.isAccountMerged; const latestError = getLatestErrorMessage(mergeWithValidateCode); - const errorKey = getMergeErrorKey(latestError); + const errorPage = getMergeErrorPage(latestError); const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -94,21 +109,24 @@ function AccountValidatePage() { useFocusEffect( useCallback(() => { - if (!errorKey || !email) { + if (!errorPage || !email) { return; } - return Navigation.navigate(ROUTES.SETTINGS_MERGE_ACCOUNTS_RESULT.getRoute(email, errorKey), {forceReplace: true}); - }, [errorKey, email]), + return Navigation.navigate(ROUTES.SETTINGS_MERGE_ACCOUNTS_RESULT.getRoute(email, errorPage), {forceReplace: true}); + }, [errorPage, email]), ); - useFocusEffect( - useCallback(() => { - return () => { - clearMergeWithValidateCode(); - clearGetValidateCodeForAccountMerge(); - }; - }, []), - ); + useEffect(() => { + const unsubscribe = navigation.addListener('blur', () => { + clearGetValidateCodeForAccountMerge(); + clearMergeWithValidateCode(); + }); + + return unsubscribe; + }, [navigation]); + + const authenticationErrorKey = getAuthenticationErrorKey(latestError); + const validateCodeError = !errorPage && authenticationErrorKey ? {authError: translate(authenticationErrorKey)} : undefined; return ( + {translate('mergeAccountsPage.accountValidate.lossOfUnsubmittedData')} {email}. @@ -147,12 +165,12 @@ function AccountValidatePage() { mergeWithValidateCodeAction(email, code); }} sendValidateCode={() => { - requestValidationCodeForAccountMerge(email); + requestValidationCodeForAccountMerge(email, true); }} shouldSkipInitialValidation clearError={() => clearMergeWithValidateCode()} - validateError={!errorKey ? mergeWithValidateCode?.errors : undefined} - hasMagicCodeBeenSent={getValidateCodeForAccountMerge?.validateCodeSent} + validateError={validateCodeError} + hasMagicCodeBeenSent={getValidateCodeForAccountMerge?.validateCodeResent} submitButtonText={translate('mergeAccountsPage.mergeAccount')} forwardedRef={validateCodeFormRef} isLoading={mergeWithValidateCode?.isLoading} diff --git a/src/types/onyx/Account.ts b/src/types/onyx/Account.ts index 8415e2b825b4..db83f4719793 100644 --- a/src/types/onyx/Account.ts +++ b/src/types/onyx/Account.ts @@ -183,6 +183,9 @@ type Account = { /** Whether the user validation code was sent */ validateCodeSent?: boolean; + /** Whether the user validation code was re-sent */ + validateCodeResent?: boolean; + /** Errors while requesting the validation code */ errors: OnyxCommon.Errors; };