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;
};