From 4237497d7e803acbe52533d12e0b46c6071ee4b6 Mon Sep 17 00:00:00 2001
From: Aldo Canepa <aldo@expensify.com>
Date: Wed, 8 Mar 2023 11:31:19 -0800
Subject: [PATCH 01/28] No need to call Navigate.navigate

---
 .../settings/Profile/Contacts/ContactMethodDetailsPage.js   | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
index 2ae32c9a4e3a..af52e44e07a4 100644
--- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
+++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
@@ -118,8 +118,12 @@ class ContactMethodDetailsPage extends Component {
     confirmDeleteAndHideModal() {
         const contactMethod = this.getContactMethod();
         User.deleteContactMethod(contactMethod);
+
+        // We only need to close the modal, calling Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS)
+        // is not necessary because we will get navigate automatically when the contact method is removed from Onyx.
+        // Using Navigation.navigate cause the Navigation.isNavigating to get stuck with a true value blocking further
+        // navigations.
         this.toggleDeleteModal(false);
-        Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS);
     }
 
     /**

From 99a10acf856f27ec5b799aeb3da3943348e263cc Mon Sep 17 00:00:00 2001
From: Aldo Canepa <aldo@expensify.com>
Date: Wed, 8 Mar 2023 12:14:34 -0800
Subject: [PATCH 02/28] Redirect on componentDidUpdate instead of render

---
 .../Contacts/ContactMethodDetailsPage.js      | 33 ++++++++++++++-----
 1 file changed, 25 insertions(+), 8 deletions(-)

diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
index af52e44e07a4..4996f55dfd2a 100644
--- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
+++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
@@ -25,6 +25,7 @@ import colors from '../../../../styles/colors';
 import Button from '../../../../components/Button';
 import * as ErrorUtils from '../../../../libs/ErrorUtils';
 import themeColors from '../../../../styles/themes/default';
+import FullScreenLoadingIndicator from '../../../../components/FullscreenLoadingIndicator';
 
 const propTypes = {
     /* Onyx Props */
@@ -89,7 +90,14 @@ class ContactMethodDetailsPage extends Component {
     }
 
     componentDidMount() {
-        if (this.getContactMethod()) {
+        if (this.doesContactMethodExist()) {
+            return;
+        }
+        Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS);
+    }
+
+    componentDidUpdate() {
+        if (this.doesContactMethodExist()) {
             return;
         }
         Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS);
@@ -104,6 +112,20 @@ class ContactMethodDetailsPage extends Component {
         return decodeURIComponent(lodashGet(this.props.route, 'params.contactMethod'));
     }
 
+    /**
+     * Checks that we have a contact method in the route params and that we can find the login data in Onyx
+     *
+     * @returns {boolean}
+     */
+    doesContactMethodExist() {
+        const contactMethod = this.getContactMethod();
+        const loginData = this.props.loginList[contactMethod];
+        if (!contactMethod || !loginData) {
+            return false;
+        }
+        return true;
+    }
+
     /**
      * Toggle delete confirm modal visibility
      * @param {Boolean} shouldOpen
@@ -118,12 +140,8 @@ class ContactMethodDetailsPage extends Component {
     confirmDeleteAndHideModal() {
         const contactMethod = this.getContactMethod();
         User.deleteContactMethod(contactMethod);
-
-        // We only need to close the modal, calling Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS)
-        // is not necessary because we will get navigate automatically when the contact method is removed from Onyx.
-        // Using Navigation.navigate cause the Navigation.isNavigating to get stuck with a true value blocking further
-        // navigations.
         this.toggleDeleteModal(false);
+        Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS);
     }
 
     /**
@@ -144,8 +162,7 @@ class ContactMethodDetailsPage extends Component {
         const contactMethod = this.getContactMethod();
         const loginData = this.props.loginList[contactMethod];
         if (!contactMethod || !loginData) {
-            Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS);
-            return null;
+            return <FullScreenLoadingIndicator />;
         }
 
         const isDefaultContactMethod = (this.props.session.email === loginData.partnerUserID);

From a2056b589fb4b7ce9ed26b3dd958726e2d42b79f Mon Sep 17 00:00:00 2001
From: Alex Beaman <alexbeaman@expensify.com>
Date: Thu, 9 Mar 2023 18:45:06 +0200
Subject: [PATCH 03/28] Add everything back that got reverted

---
 src/ROUTES.js                                 |   2 +
 src/components/AvatarWithIndicator.js         |  49 +++-
 src/components/MenuItem.js                    |   2 +-
 src/languages/en.js                           |  16 ++
 src/languages/es.js                           |  16 ++
 src/libs/ErrorUtils.js                        |  23 ++
 .../AppNavigator/ModalStackNavigators.js      |   7 +
 src/libs/Navigation/linkingConfig.js          |   3 +
 src/libs/UserUtils.js                         |  68 +++++
 src/libs/actions/User.js                      | 187 +++++++++++++
 src/pages/settings/InitialSettingsPage.js     |  16 +-
 .../Contacts/ContactMethodDetailsPage.js      | 257 ++++++++++++++++++
 .../Profile/Contacts/ContactMethodsPage.js    | 178 ++++++------
 src/pages/settings/Profile/ProfilePage.js     |  24 ++
 src/styles/styles.js                          |  23 +-
 src/styles/utilities/spacing.js               |   4 +
 16 files changed, 763 insertions(+), 112 deletions(-)
 create mode 100644 src/libs/UserUtils.js
 create mode 100644 src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js

diff --git a/src/ROUTES.js b/src/ROUTES.js
index 0b70b92c1d6f..ab7fef023204 100644
--- a/src/ROUTES.js
+++ b/src/ROUTES.js
@@ -51,6 +51,8 @@ export default {
     SETTINGS_PERSONAL_DETAILS_DATE_OF_BIRTH: `${SETTINGS_PERSONAL_DETAILS}/date-of-birth`,
     SETTINGS_PERSONAL_DETAILS_ADDRESS: `${SETTINGS_PERSONAL_DETAILS}/address`,
     SETTINGS_CONTACT_METHODS,
+    SETTINGS_CONTACT_METHOD_DETAILS: `${SETTINGS_CONTACT_METHODS}/:contactMethod/details`,
+    getEditContactMethodRoute: contactMethod => `${SETTINGS_CONTACT_METHODS}/${encodeURIComponent(contactMethod)}/details`,
     NEW_GROUP: 'new/group',
     NEW_CHAT: 'new/chat',
     REPORT,
diff --git a/src/components/AvatarWithIndicator.js b/src/components/AvatarWithIndicator.js
index 6430d463a712..f2a92f9df98c 100644
--- a/src/components/AvatarWithIndicator.js
+++ b/src/components/AvatarWithIndicator.js
@@ -15,6 +15,8 @@ import {policyPropTypes} from '../pages/workspace/withPolicy';
 import walletTermsPropTypes from '../pages/EnablePayments/walletTermsPropTypes';
 import * as PolicyUtils from '../libs/PolicyUtils';
 import * as PaymentMethods from '../libs/actions/PaymentMethods';
+import * as UserUtils from '../libs/UserUtils';
+import themeColors from '../styles/themes/default';
 
 const propTypes = {
     /** URL for the avatar */
@@ -26,6 +28,8 @@ const propTypes = {
     /** To show a tooltip on hover */
     tooltipText: PropTypes.string,
 
+    /* Onyx Props */
+
     /** The employee list of all policies (coming from Onyx) */
     policiesMemberList: PropTypes.objectOf(policyMemberPropType),
 
@@ -43,6 +47,15 @@ const propTypes = {
 
     /** Information about the user accepting the terms for payments */
     walletTerms: walletTermsPropTypes,
+
+    /** Login list for the user that is signed in */
+    loginList: PropTypes.shape({
+        /** Date login was validated, used to show info indicator status */
+        validatedDate: PropTypes.string,
+
+        /** Field-specific server side errors keyed by microtime */
+        errorFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)),
+    }),
 };
 
 const defaultProps = {
@@ -54,45 +67,50 @@ const defaultProps = {
     cardList: {},
     userWallet: {},
     walletTerms: {},
+    loginList: {},
 };
 
 const AvatarWithIndicator = (props) => {
-    const isLarge = props.size === 'large';
-    const indicatorStyles = [
-        styles.alignItemsCenter,
-        styles.justifyContentCenter,
-        isLarge ? styles.statusIndicatorLarge : styles.statusIndicator,
-    ];
-
     // If a policy was just deleted from Onyx, then Onyx will pass a null value to the props, and
     // those should be cleaned out before doing any error checking
     const cleanPolicies = _.pick(props.policies, policy => policy);
     const cleanPolicyMembers = _.pick(props.policiesMemberList, member => member);
 
-    // All of the error-checking methods are put into an array. This is so that using _.some() will return
-    // early as soon as the first error is returned. This makes the error checking very efficient since
-    // we only care if a single error exists anywhere.
+    // All of the error & info-checking methods are put into an array. This is so that using _.some() will return
+    // early as soon as the first error / info condition is returned. This makes the checks very efficient since
+    // we only care if a single error / info condition exists anywhere.
     const errorCheckingMethods = [
         () => !_.isEmpty(props.userWallet.errors),
         () => PaymentMethods.hasPaymentMethodError(props.bankAccountList, props.cardList),
         () => _.some(cleanPolicies, PolicyUtils.hasPolicyError),
         () => _.some(cleanPolicies, PolicyUtils.hasCustomUnitsError),
         () => _.some(cleanPolicyMembers, PolicyUtils.hasPolicyMemberError),
+        () => UserUtils.hasLoginListError(props.loginList),
 
         // Wallet term errors that are not caused by an IOU (we show the red brick indicator for those in the LHN instead)
         () => !_.isEmpty(props.walletTerms.errors) && !props.walletTerms.chatReportID,
     ];
-    const shouldShowIndicator = _.some(errorCheckingMethods, errorCheckingMethod => errorCheckingMethod());
+    const infoCheckingMethods = [
+        () => UserUtils.hasLoginListInfo(props.loginList),
+    ];
+    const shouldShowErrorIndicator = _.some(errorCheckingMethods, errorCheckingMethod => errorCheckingMethod());
+    const shouldShowInfoIndicator = !shouldShowErrorIndicator && _.some(infoCheckingMethods, infoCheckingMethod => infoCheckingMethod());
+
+    const indicatorColor = shouldShowErrorIndicator ? themeColors.danger : themeColors.success;
+    const indicatorStyles = [
+        styles.alignItemsCenter,
+        styles.justifyContentCenter,
+        styles.statusIndicator(indicatorColor),
+    ];
 
     return (
-        <View style={[isLarge ? styles.avatarLarge : styles.sidebarAvatar]}>
+        <View style={[styles.sidebarAvatar]}>
             <Tooltip text={props.tooltipText}>
                 <Avatar
-                    imageStyles={[isLarge ? styles.avatarLarge : null]}
                     source={props.source}
                     size={props.size}
                 />
-                {shouldShowIndicator && (
+                {(shouldShowErrorIndicator || shouldShowInfoIndicator) && (
                     <View style={StyleSheet.flatten(indicatorStyles)} />
                 )}
             </Tooltip>
@@ -123,4 +141,7 @@ export default withOnyx({
     walletTerms: {
         key: ONYXKEYS.WALLET_TERMS,
     },
+    loginList: {
+        key: ONYXKEYS.LOGIN_LIST,
+    },
 })(AvatarWithIndicator);
diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js
index 33563bb029be..b23cf1ba6f8c 100644
--- a/src/components/MenuItem.js
+++ b/src/components/MenuItem.js
@@ -62,7 +62,7 @@ const MenuItem = (props) => {
     const descriptionTextStyle = StyleUtils.combineStyles([
         styles.textLabelSupporting,
         (props.icon ? styles.ml3 : undefined),
-        styles.breakAll,
+        styles.breakWord,
         styles.lineHeightNormal,
     ], props.style);
 
diff --git a/src/languages/en.js b/src/languages/en.js
index a47b9410a000..66dde64994c8 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -119,6 +119,8 @@ export default {
         youAppearToBeOffline: 'You appear to be offline.',
         thisFeatureRequiresInternet: 'This feature requires an active internet connection to be used.',
         areYouSure: 'Are you sure?',
+        verify: 'Verify',
+        yesContinue: 'Yes, continue',
         zipCodeExample: 'e.g. 12345, 12345-1234, 12345 1234',
         websiteExample: 'e.g. https://www.expensify.com',
     },
@@ -344,6 +346,20 @@ export default {
     contacts: {
         contactMethod: 'Contact method',
         contactMethods: 'Contact methods',
+        helpTextBeforeEmail: 'Add more ways for people to find you, and forward receipts to ',
+        helpTextAfterEmail: ' from multiple email addresses.',
+        pleaseVerify: 'Please verify this contact method',
+        getInTouch: "Whenever we need to get in touch with you, we'll use this contact method.",
+        enterMagicCode: ({contactMethod}) => `Please enter the magic code sent to ${contactMethod}`,
+        yourDefaultContactMethod: 'This is your current default contact method. You will not be able to delete this contact method until you set an alternative default by selecting another contact method and pressing “Set as default”.',
+        removeContactMethod: 'Remove contact method',
+        removeAreYouSure: 'Are you sure you want to remove this contact method? This action cannot be undone.',
+        resendMagicCode: 'Resend magic code',
+        genericFailureMessages: {
+            requestContactMethodValidateCode: 'Failed to send a new magic code. Please wait a bit and try again.',
+            validateSecondaryLogin: 'Failed to validate contact method with given magic code. Please request a new code and try again.',
+            deleteContactMethod: 'Failed to delete contact method. Please reach out to Concierge for help.',
+        },
     },
     pronouns: {
         coCos: 'Co / Cos',
diff --git a/src/languages/es.js b/src/languages/es.js
index 7c80857ec75e..4083baed31d3 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -118,6 +118,8 @@ export default {
         youAppearToBeOffline: 'Parece que estás desconectado.',
         thisFeatureRequiresInternet: 'Esta función requiere una conexión a Internet activa para ser utilizada.',
         areYouSure: '¿Estás seguro?',
+        verify: 'Verifique',
+        yesContinue: 'Sí, Continuar',
         zipCodeExample: 'p. ej. 12345, 12345-1234, 12345 1234',
         websiteExample: 'p. ej. https://www.expensify.com',
     },
@@ -343,6 +345,20 @@ export default {
     contacts: {
         contactMethod: 'Método de contacto',
         contactMethods: 'Métodos de contacto',
+        helpTextBeforeEmail: 'Añade más formas de que la gente te encuentre y reenvía los recibos a ',
+        helpTextAfterEmail: ' desde varias direcciones de correo electrónico.',
+        pleaseVerify: 'Por favor verifica este método de contacto',
+        getInTouch: 'Utilizaremos este método de contacto cuando necesitemos contactarte.',
+        enterMagicCode: ({contactMethod}) => `Por favor, introduce el código mágico enviado a ${contactMethod}`,
+        yourDefaultContactMethod: 'Este es tu método de contacto predeterminado. No podrás eliminarlo hasta que añadas otro método de contacto y lo marques como predeterminado pulsando "Establecer como predeterminado".',
+        removeContactMethod: 'Eliminar método de contacto',
+        removeAreYouSure: '¿Estás seguro de que quieres eliminar este método de contacto? Esta acción no se puede deshacer.',
+        resendMagicCode: 'Reenviar código mágico',
+        genericFailureMessages: {
+            requestContactMethodValidateCode: 'No se ha podido enviar un nuevo código mágico. Espera un rato y vuelve a intentarlo.',
+            validateSecondaryLogin: 'No se ha podido validar el método de contacto con el código mágico provisto. Solicita un nuevo código y vuelve a intentarlo.',
+            deleteContactMethod: 'No se ha podido eliminar el método de contacto. Por favor contacta con Concierge para obtener ayuda.',
+        },
     },
     pronouns: {
         coCos: 'Co / Cos',
diff --git a/src/libs/ErrorUtils.js b/src/libs/ErrorUtils.js
index e1ca7233ba8a..8173c57ad8a6 100644
--- a/src/libs/ErrorUtils.js
+++ b/src/libs/ErrorUtils.js
@@ -1,4 +1,5 @@
 import _ from 'underscore';
+import lodashGet from 'lodash/get';
 import CONST from '../CONST';
 
 /**
@@ -54,8 +55,30 @@ function getLatestErrorMessage(onyxData) {
         .value();
 }
 
+/**
+ * @param {Object} onyxData
+ * @param {Object} onyxData.errorFields
+ * @param {String} fieldName
+ * @returns {Object}
+ */
+function getLatestErrorField(onyxData, fieldName) {
+    const errorsForField = lodashGet(onyxData, ['errorFields', fieldName], {});
+
+    if (_.isEmpty(errorsForField)) {
+        return {};
+    }
+    return _.chain(errorsForField)
+        .keys()
+        .sortBy()
+        .reverse()
+        .map(key => ({[key]: errorsForField[key]}))
+        .first()
+        .value();
+}
+
 export {
     // eslint-disable-next-line import/prefer-default-export
     getAuthenticateErrorMessage,
     getLatestErrorMessage,
+    getLatestErrorField,
 };
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
index fecb903eafd1..a177b67bc86a 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
@@ -280,6 +280,13 @@ const SettingsModalStackNavigator = createModalStackNavigator([
         },
         name: 'Settings_ContactMethods',
     },
+    {
+        getComponent: () => {
+            const SettingsContactMethodDetailsPage = require('../../../pages/settings/Profile/Contacts/ContactMethodDetailsPage').default;
+            return SettingsContactMethodDetailsPage;
+        },
+        name: 'Settings_ContactMethodDetails',
+    },
     {
         getComponent: () => {
             const SettingsAddSecondaryLoginPage = require('../../../pages/settings/Profile/Contacts/AddSecondaryLoginPage').default;
diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js
index 3af0f479ae17..f4d038c7ade0 100644
--- a/src/libs/Navigation/linkingConfig.js
+++ b/src/libs/Navigation/linkingConfig.js
@@ -124,6 +124,9 @@ export default {
                         path: ROUTES.SETTINGS_CONTACT_METHODS,
                         exact: true,
                     },
+                    Settings_ContactMethodDetails: {
+                        path: ROUTES.SETTINGS_CONTACT_METHOD_DETAILS,
+                    },
                     Settings_Add_Secondary_Login: {
                         path: ROUTES.SETTINGS_ADD_LOGIN,
                     },
diff --git a/src/libs/UserUtils.js b/src/libs/UserUtils.js
new file mode 100644
index 000000000000..748ad18dac69
--- /dev/null
+++ b/src/libs/UserUtils.js
@@ -0,0 +1,68 @@
+import _ from 'underscore';
+import lodashGet from 'lodash/get';
+import CONST from '../CONST';
+
+/**
+ * Searches through given loginList for any contact method / login with an error.
+ *
+ * Example that should return false:
+ * {{
+ *      test@test.com: {
+ *          errorFields: {
+ *              validateCodeSent: null
+ *          }
+ *      }
+ * }}
+ *
+ * Example that should return true:
+ * {{
+ *      test@test.com: {
+ *          errorFields: {
+ *              validateCodeSent: { 18092081290: 'An error' }
+ *          }
+ *      }
+ * }}
+ *
+ * @param {Object} loginList
+ * @param {Object} loginList.errorFields
+ * @returns {Boolean}
+ */
+function hasLoginListError(loginList) {
+    return _.some(loginList, login => _.some(lodashGet(login, 'errorFields', {}), field => !_.isEmpty(field)));
+}
+
+/**
+ * Searches through given loginList for any contact method / login that requires
+ * an Info brick road status indicator. Currently this only applies if the user
+ * has an unvalidated contact method.
+ *
+ * @param {Object} loginList
+ * @param {String} loginList.validatedDate
+ * @returns {Boolean}
+ */
+function hasLoginListInfo(loginList) {
+    return _.some(loginList, login => _.isEmpty(login.validatedDate));
+}
+
+/**
+ * Gets the appropriate brick road indicator status for a given loginList.
+ * Error status is higher priority, so we check for that first.
+ *
+ * @param {Object} loginList
+ * @returns {String}
+ */
+function getLoginListBrickRoadIndicator(loginList) {
+    if (hasLoginListError(loginList)) {
+        return CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR;
+    }
+    if (hasLoginListInfo(loginList)) {
+        return CONST.BRICK_ROAD_INDICATOR_STATUS.INFO;
+    }
+    return '';
+}
+
+export {
+    hasLoginListError,
+    hasLoginListInfo,
+    getLoginListBrickRoadIndicator,
+};
diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js
index 7e7b51e93d3d..01daca6f7a28 100644
--- a/src/libs/actions/User.js
+++ b/src/libs/actions/User.js
@@ -19,6 +19,7 @@ import * as SequentialQueue from '../Network/SequentialQueue';
 import PusherUtils from '../PusherUtils';
 import * as Report from './Report';
 import * as ReportActionsUtils from '../ReportActionsUtils';
+import DateUtils from '../DateUtils';
 import * as Session from './Session';
 
 let currentUserAccountID = '';
@@ -100,6 +101,61 @@ function resendValidateCode(login) {
     Session.resendValidateCode(login);
 }
 
+/**
+ * Requests a new validate code be sent for the passed contact method
+ *
+ * @param {String} contactMethod - the new contact method that the user is trying to verify
+ */
+function requestContactMethodValidateCode(contactMethod) {
+    const optimisticData = [{
+        onyxMethod: CONST.ONYX.METHOD.MERGE,
+        key: ONYXKEYS.LOGIN_LIST,
+        value: {
+            [contactMethod]: {
+                validateCodeSent: true,
+                errorFields: {
+                    validateCodeSent: null,
+                },
+                pendingFields: {
+                    validateCodeSent: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+                },
+            },
+        },
+    }];
+    const successData = [{
+        onyxMethod: CONST.ONYX.METHOD.MERGE,
+        key: ONYXKEYS.LOGIN_LIST,
+        value: {
+            [contactMethod]: {
+                pendingFields: {
+                    validateCodeSent: null,
+                },
+            },
+        },
+    }];
+    const failureData = [{
+        onyxMethod: CONST.ONYX.METHOD.MERGE,
+        key: ONYXKEYS.LOGIN_LIST,
+        value: {
+            [contactMethod]: {
+                validateCodeSent: false,
+                errorFields: {
+                    validateCodeSent: {
+                        [DateUtils.getMicroseconds()]: Localize.translateLocal('contacts.genericFailureMessages.requestContactMethodValidateCode'),
+                    },
+                },
+                pendingFields: {
+                    validateCodeSent: null,
+                },
+            },
+        },
+    }];
+
+    API.write('RequestContactMethodValidateCode', {
+        email: contactMethod,
+    }, {optimisticData, successData, failureData});
+}
+
 /**
  * Sets whether or not the user is subscribed to Expensify news
  *
@@ -162,6 +218,77 @@ function setSecondaryLoginAndNavigate(login, password) {
     });
 }
 
+/**
+ * Delete a specific contact method
+ *
+ * @param {String} contactMethod - the contact method being deleted
+ * @param {Object} oldLoginData
+ */
+function deleteContactMethod(contactMethod, oldLoginData) {
+    const optimisticData = [{
+        onyxMethod: CONST.ONYX.METHOD.MERGE,
+        key: ONYXKEYS.LOGIN_LIST,
+        value: {
+            [contactMethod]: {
+                partnerUserID: '',
+                errorFields: {
+                    deletedLogin: null,
+                },
+                pendingFields: {
+                    deletedLogin: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
+                },
+            },
+        },
+    }];
+    const successData = [{
+        onyxMethod: CONST.ONYX.METHOD.MERGE,
+        key: ONYXKEYS.LOGIN_LIST,
+        value: {
+            [contactMethod]: null,
+        },
+    }];
+    const failureData = [{
+        onyxMethod: CONST.ONYX.METHOD.MERGE,
+        key: ONYXKEYS.LOGIN_LIST,
+        value: {
+            [contactMethod]: {
+                ...oldLoginData,
+                errorFields: {
+                    deletedLogin: {
+                        [DateUtils.getMicroseconds()]: Localize.translateLocal('contacts.genericFailureMessages.deleteContactMethod'),
+                    },
+                },
+                pendingFields: {
+                    deletedLogin: null,
+                },
+            },
+        },
+    }];
+
+    API.write('DeleteContactMethod', {
+        partnerUserID: contactMethod,
+    }, {optimisticData, successData, failureData});
+}
+
+/**
+ * Clears any possible stored errors for a specific field on a contact method
+ *
+ * @param {String} contactMethod
+ * @param {String} fieldName
+ */
+function clearContactMethodErrors(contactMethod, fieldName) {
+    Onyx.merge(ONYXKEYS.LOGIN_LIST, {
+        [contactMethod]: {
+            errorFields: {
+                [fieldName]: null,
+            },
+            pendingFields: {
+                [fieldName]: null,
+            },
+        },
+    });
+}
+
 /**
  * Validates a login given an accountID and validation code
  *
@@ -187,6 +314,62 @@ function validateLogin(accountID, validateCode) {
     Navigation.navigate(ROUTES.HOME);
 }
 
+/**
+ * Validates a secondary login / contact method
+ *
+ * @param {String} contactMethod - The contact method the user is trying to verify
+ * @param {String} validateCode
+ */
+function validateSecondaryLogin(contactMethod, validateCode) {
+    const optimisticData = [{
+        onyxMethod: CONST.ONYX.METHOD.MERGE,
+        key: ONYXKEYS.LOGIN_LIST,
+        value: {
+            [contactMethod]: {
+                errorFields: {
+                    validateLogin: null,
+                },
+                pendingFields: {
+                    validateLogin: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+                },
+            },
+        },
+    }];
+    const successData = [{
+        onyxMethod: CONST.ONYX.METHOD.MERGE,
+        key: ONYXKEYS.LOGIN_LIST,
+        value: {
+            [contactMethod]: {
+                pendingFields: {
+                    validateLogin: null,
+                },
+            },
+        },
+    }];
+    const failureData = [{
+        onyxMethod: CONST.ONYX.METHOD.MERGE,
+        key: ONYXKEYS.LOGIN_LIST,
+        value: {
+            [contactMethod]: {
+                errorFields: {
+                    validateLogin: {
+                        [DateUtils.getMicroseconds()]: Localize.translateLocal('contacts.genericFailureMessages.validateSecondaryLogin'),
+                    },
+                },
+                pendingFields: {
+                    validateLogin: null,
+                },
+            },
+        },
+    }];
+
+    API.write('ValidateSecondaryLogin', {
+        partnerUserID: contactMethod,
+        validateCode,
+    }, {optimisticData, successData, failureData});
+    Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS);
+}
+
 /**
  * Checks the blockedFromConcierge object to see if it has an expiresAt key,
  * and if so whether the expiresAt date of a user's ban is before right now
@@ -474,9 +657,13 @@ export {
     updatePassword,
     closeAccount,
     resendValidateCode,
+    requestContactMethodValidateCode,
     updateNewsletterSubscription,
     setSecondaryLoginAndNavigate,
+    deleteContactMethod,
+    clearContactMethodErrors,
     validateLogin,
+    validateSecondaryLogin,
     isBlockedFromConcierge,
     subscribeToUserEvents,
     updatePreferredSkinTone,
diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js
index 6d6db5a498f3..36a6b10ce194 100755
--- a/src/pages/settings/InitialSettingsPage.js
+++ b/src/pages/settings/InitialSettingsPage.js
@@ -34,6 +34,7 @@ import ConfirmModal from '../../components/ConfirmModal';
 import * as ReportUtils from '../../libs/ReportUtils';
 import * as Link from '../../libs/actions/Link';
 import OfflineWithFeedback from '../../components/OfflineWithFeedback';
+import * as UserUtils from '../../libs/UserUtils';
 
 const propTypes = {
     /* Onyx Props */
@@ -80,6 +81,15 @@ const propTypes = {
     /** Information about the user accepting the terms for payments */
     walletTerms: walletTermsPropTypes,
 
+    /** Login list for the user that is signed in */
+    loginList: PropTypes.shape({
+        /** Date login was validated, used to show brickroad info status */
+        validatedDate: PropTypes.string,
+
+        /** Field-specific server side errors keyed by microtime */
+        errorFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)),
+    }),
+
     ...withLocalizePropTypes,
     ...withCurrentUserPersonalDetailsPropTypes,
 };
@@ -145,6 +155,7 @@ class InitialSettingsPage extends React.Component {
             .filter(policy => policy && policy.type === CONST.POLICY.TYPE.FREE && policy.role === CONST.POLICY.ROLE.ADMIN)
             .find(policy => PolicyUtils.hasPolicyError(policy) || PolicyUtils.getPolicyBrickRoadIndicatorStatus(policy, this.props.policyMembers))
             .value() ? 'error' : null;
+        const profileBrickRoadIndicator = UserUtils.getLoginListBrickRoadIndicator(this.props.loginList);
 
         return ([
             {
@@ -159,6 +170,7 @@ class InitialSettingsPage extends React.Component {
                 translationKey: 'common.profile',
                 icon: Expensicons.Profile,
                 action: () => { App.openProfile(); },
+                brickRoadIndicator: profileBrickRoadIndicator,
             },
             {
                 translationKey: 'common.preferences',
@@ -209,7 +221,6 @@ class InitialSettingsPage extends React.Component {
                 iconType={item.iconType}
                 onPress={item.action}
                 iconStyles={item.iconStyles}
-                iconFill={item.iconFill}
                 shouldShowRightIcon
                 iconRight={item.iconRight}
                 badgeText={this.getWalletBalance(isPaymentItem)}
@@ -342,6 +353,9 @@ export default compose(
         walletTerms: {
             key: ONYXKEYS.WALLET_TERMS,
         },
+        loginList: {
+            key: ONYXKEYS.LOGIN_LIST,
+        },
     }),
     withNetwork(),
 )(InitialSettingsPage);
diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
new file mode 100644
index 000000000000..2ae32c9a4e3a
--- /dev/null
+++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
@@ -0,0 +1,257 @@
+import Str from 'expensify-common/lib/str';
+import lodashGet from 'lodash/get';
+import React, {Component} from 'react';
+import {View, ScrollView, TouchableOpacity} from 'react-native';
+import PropTypes from 'prop-types';
+import {withOnyx} from 'react-native-onyx';
+import Navigation from '../../../../libs/Navigation/Navigation';
+import ROUTES from '../../../../ROUTES';
+import ScreenWrapper from '../../../../components/ScreenWrapper';
+import HeaderWithCloseButton from '../../../../components/HeaderWithCloseButton';
+import compose from '../../../../libs/compose';
+import ONYXKEYS from '../../../../ONYXKEYS';
+import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize';
+import MenuItem from '../../../../components/MenuItem';
+import styles from '../../../../styles/styles';
+import * as Expensicons from '../../../../components/Icon/Expensicons';
+import Text from '../../../../components/Text';
+import OfflineWithFeedback from '../../../../components/OfflineWithFeedback';
+import ConfirmModal from '../../../../components/ConfirmModal';
+import * as User from '../../../../libs/actions/User';
+import TextInput from '../../../../components/TextInput';
+import CONST from '../../../../CONST';
+import Icon from '../../../../components/Icon';
+import colors from '../../../../styles/colors';
+import Button from '../../../../components/Button';
+import * as ErrorUtils from '../../../../libs/ErrorUtils';
+import themeColors from '../../../../styles/themes/default';
+
+const propTypes = {
+    /* Onyx Props */
+
+    /** Login list for the user that is signed in */
+    loginList: PropTypes.shape({
+        /** Value of partner name */
+        partnerName: PropTypes.string,
+
+        /** Phone/Email associated with user */
+        partnerUserID: PropTypes.string,
+
+        /** Date when login was validated */
+        validatedDate: PropTypes.string,
+
+        /** Field-specific server side errors keyed by microtime */
+        errorFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)),
+
+        /** Field-specific pending states for offline UI status */
+        pendingFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)),
+    }),
+
+    /** Current user session */
+    session: PropTypes.shape({
+        email: PropTypes.string.isRequired,
+    }).isRequired,
+
+    /** Route params */
+    route: PropTypes.shape({
+        params: PropTypes.shape({
+            /** passed via route /settings/profile/contact-methods/:contactMethod/details */
+            contactMethod: PropTypes.string,
+        }),
+    }),
+
+    ...withLocalizePropTypes,
+};
+
+const defaultProps = {
+    loginList: {},
+    route: {
+        params: {
+            contactMethod: '',
+        },
+    },
+};
+
+class ContactMethodDetailsPage extends Component {
+    constructor(props) {
+        super(props);
+
+        this.toggleDeleteModal = this.toggleDeleteModal.bind(this);
+        this.confirmDeleteAndHideModal = this.confirmDeleteAndHideModal.bind(this);
+        this.resendValidateCode = this.resendValidateCode.bind(this);
+        this.getContactMethod = this.getContactMethod.bind(this);
+        this.validateContactMethod = this.validateContactMethod.bind(this);
+
+        this.state = {
+            isDeleteModalOpen: false,
+            validateCode: '',
+        };
+    }
+
+    componentDidMount() {
+        if (this.getContactMethod()) {
+            return;
+        }
+        Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS);
+    }
+
+    /**
+     * Gets the current contact method from the route params
+     *
+     * @returns {string}
+     */
+    getContactMethod() {
+        return decodeURIComponent(lodashGet(this.props.route, 'params.contactMethod'));
+    }
+
+    /**
+     * Toggle delete confirm modal visibility
+     * @param {Boolean} shouldOpen
+     */
+    toggleDeleteModal(shouldOpen) {
+        this.setState({isDeleteModalOpen: shouldOpen});
+    }
+
+    /**
+     * Delete the contact method and hide the modal
+     */
+    confirmDeleteAndHideModal() {
+        const contactMethod = this.getContactMethod();
+        User.deleteContactMethod(contactMethod);
+        this.toggleDeleteModal(false);
+        Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS);
+    }
+
+    /**
+     * Request a validate code / magic code be sent to verify this contact method
+     */
+    resendValidateCode() {
+        User.requestContactMethodValidateCode(this.getContactMethod());
+    }
+
+    /**
+     * Attempt to validate this contact method
+     */
+    validateContactMethod() {
+        User.validateSecondaryLogin(this.getContactMethod(), this.state.validateCode);
+    }
+
+    render() {
+        const contactMethod = this.getContactMethod();
+        const loginData = this.props.loginList[contactMethod];
+        if (!contactMethod || !loginData) {
+            Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS);
+            return null;
+        }
+
+        const isDefaultContactMethod = (this.props.session.email === loginData.partnerUserID);
+        const hasMagicCodeBeenSent = lodashGet(this.props.loginList, [contactMethod, 'validateCodeSent'], false);
+
+        return (
+            <ScreenWrapper>
+                <HeaderWithCloseButton
+                    title={Str.removeSMSDomain(contactMethod)}
+                    shouldShowBackButton
+                    onBackButtonPress={() => Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS)}
+                    onCloseButtonPress={() => Navigation.dismissModal(true)}
+                />
+                <ScrollView>
+                    <ConfirmModal
+                        title={this.props.translate('contacts.removeContactMethod')}
+                        onConfirm={this.confirmDeleteAndHideModal}
+                        onCancel={() => this.toggleDeleteModal(false)}
+                        prompt={this.props.translate('contacts.removeAreYouSure')}
+                        confirmText={this.props.translate('common.yesContinue')}
+                        isVisible={this.state.isDeleteModalOpen}
+                        danger
+                    />
+                    {!loginData.validatedDate && (
+                        <View style={[styles.mh5, styles.mb7]}>
+                            <View style={[styles.flexRow, styles.alignItemsCenter, styles.mb1, styles.mt3]}>
+                                <Icon src={Expensicons.DotIndicator} fill={colors.green} />
+                                <View style={[styles.flex1, styles.ml4]}>
+                                    <Text>
+                                        {this.props.translate('contacts.enterMagicCode', {contactMethod})}
+                                    </Text>
+                                </View>
+                            </View>
+                            <TextInput
+                                label={this.props.translate('common.magicCode')}
+                                name="validateCode"
+                                value={this.state.validateCode}
+                                onChangeText={text => this.setState({validateCode: text})}
+                                keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD}
+                                blurOnSubmit={false}
+                            />
+                            <OfflineWithFeedback
+                                pendingAction={lodashGet(loginData, 'pendingFields.validateCodeSent', null)}
+                                errors={ErrorUtils.getLatestErrorField(loginData, 'validateCodeSent')}
+                                errorRowStyles={[styles.mt2]}
+                                onClose={() => User.clearContactMethodErrors(contactMethod, 'validateCodeSent')}
+                            >
+                                <TouchableOpacity
+                                    style={[styles.mt2, styles.dFlex, styles.flexRow]}
+                                    onPress={this.resendValidateCode}
+                                >
+                                    <Text style={[styles.link, styles.mr4]}>
+                                        {this.props.translate('contacts.resendMagicCode')}
+                                    </Text>
+                                    {hasMagicCodeBeenSent && (
+                                        <Icon src={Expensicons.Checkmark} fill={colors.green} />
+                                    )}
+                                </TouchableOpacity>
+                            </OfflineWithFeedback>
+                            <OfflineWithFeedback
+                                pendingAction={lodashGet(loginData, 'pendingFields.validateLogin', null)}
+                                errors={ErrorUtils.getLatestErrorField(loginData, 'validateLogin')}
+                                errorRowStyles={[styles.mt2]}
+                                onClose={() => User.clearContactMethodErrors(contactMethod, 'validateLogin')}
+                            >
+                                <Button
+                                    text={this.props.translate('common.verify')}
+                                    onPress={this.validateContactMethod}
+                                    style={[styles.mt4]}
+                                    success
+                                />
+                            </OfflineWithFeedback>
+                        </View>
+                    )}
+                    {isDefaultContactMethod ? (
+                        <Text style={[styles.ph5]}>
+                            {this.props.translate('contacts.yourDefaultContactMethod')}
+                        </Text>
+                    ) : (
+                        <OfflineWithFeedback
+                            pendingAction={lodashGet(loginData, 'pendingFields.deletedLogin', null)}
+                            errors={ErrorUtils.getLatestErrorField(loginData, 'deletedLogin')}
+                            errorRowStyles={[styles.mt6]}
+                            onClose={() => User.clearContactMethodErrors(contactMethod, 'deletedLogin')}
+                        >
+                            <MenuItem
+                                title={this.props.translate('common.remove')}
+                                icon={Expensicons.Trashcan}
+                                iconFill={themeColors.danger}
+                                onPress={() => this.toggleDeleteModal(true)}
+                            />
+                        </OfflineWithFeedback>
+                    )}
+                </ScrollView>
+            </ScreenWrapper>
+        );
+    }
+}
+
+ContactMethodDetailsPage.propTypes = propTypes;
+ContactMethodDetailsPage.defaultProps = defaultProps;
+
+export default compose(
+    withLocalize,
+    withOnyx({
+        loginList: {
+            key: ONYXKEYS.LOGIN_LIST,
+        },
+        session: {
+            key: ONYXKEYS.SESSION,
+        },
+    }),
+)(ContactMethodDetailsPage);
diff --git a/src/pages/settings/Profile/Contacts/ContactMethodsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodsPage.js
index edb998047c11..63645f0e54ee 100644
--- a/src/pages/settings/Profile/Contacts/ContactMethodsPage.js
+++ b/src/pages/settings/Profile/Contacts/ContactMethodsPage.js
@@ -1,13 +1,13 @@
 import Str from 'expensify-common/lib/str';
 import lodashGet from 'lodash/get';
 import PropTypes from 'prop-types';
-import React, {Component} from 'react';
+import React from 'react';
+import {View} from 'react-native';
 import {ScrollView} from 'react-native-gesture-handler';
 import {withOnyx} from 'react-native-onyx';
 import _ from 'underscore';
 import HeaderWithCloseButton from '../../../../components/HeaderWithCloseButton';
 import ScreenWrapper from '../../../../components/ScreenWrapper';
-import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '../../../../components/withCurrentUserPersonalDetails';
 import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize';
 import CONST from '../../../../CONST';
 import compose from '../../../../libs/compose';
@@ -15,6 +15,11 @@ import Navigation from '../../../../libs/Navigation/Navigation';
 import ONYXKEYS from '../../../../ONYXKEYS';
 import ROUTES from '../../../../ROUTES';
 import LoginField from './LoginField';
+import MenuItem from '../../../../components/MenuItem';
+import Text from '../../../../components/Text';
+import styles from '../../../../styles/styles';
+import CopyTextToClipboard from '../../../../components/CopyTextToClipboard';
+import OfflineWithFeedback from '../../../../components/OfflineWithFeedback';
 
 const propTypes = {
     /* Onyx Props */
@@ -23,116 +28,133 @@ const propTypes = {
     loginList: PropTypes.shape({
         /** Value of partner name */
         partnerName: PropTypes.string,
-
         /** Phone/Email associated with user */
         partnerUserID: PropTypes.string,
 
         /** Date of when login was validated */
+        /** Date login was validated, used to show brickroad info status */
         validatedDate: PropTypes.string,
+
+        /** Field-specific server side errors keyed by microtime */
+        errorFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)),
+
+        /** Field-specific pending states for offline UI status */
+        pendingFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)),
     }),
 
+    /** Current user session */
+    session: PropTypes.shape({
+        email: PropTypes.string.isRequired,
+    }).isRequired,
+
     ...withLocalizePropTypes,
-    ...withCurrentUserPersonalDetailsPropTypes,
 };
 
 const defaultProps = {
     loginList: {},
-    ...withCurrentUserPersonalDetailsDefaultProps,
 };
 
-class ContactMethodsPage extends Component {
-    constructor(props) {
-        super(props);
+const ContactMethodsPage = (props) => {
+    let hasPhoneNumberLogin = false;
+    let hasEmailLogin = false;
 
-        this.state = {
-            logins: this.getLogins(),
-        };
-
-        this.getLogins = this.getLogins.bind(this);
-    }
-
-    componentDidUpdate(prevProps) {
-        let stateToUpdate = {};
+    const loginMenuItems = _.map(props.loginList, (login, loginName) => {
+        const pendingAction = lodashGet(login, 'pendingFields.deletedLogin', null);
+        if (!login.partnerUserID && _.isEmpty(pendingAction)) {
+            return null;
+        }
 
-        // Recalculate logins if loginList has changed
-        if (_.keys(this.props.loginList).length !== _.keys(prevProps.loginList).length) {
-            stateToUpdate = {logins: this.getLogins()};
+        let description = '';
+        if (props.session.email === login.partnerUserID) {
+            description = props.translate('contacts.getInTouch');
+        } else if (!login.validatedDate) {
+            description = props.translate('contacts.pleaseVerify');
+        }
+        let indicator = null;
+        if (_.some(lodashGet(login, 'errorFields', {}), errorField => !_.isEmpty(errorField))) {
+            indicator = CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR;
+        } else if (!login.validatedDate) {
+            indicator = CONST.BRICK_ROAD_INDICATOR_STATUS.INFO;
         }
 
-        if (_.isEmpty(stateToUpdate)) {
-            return;
+        // Temporary checks to determine if we need to show specific LoginField
+        // components. This check will be removed soon.
+        // Also we still use login.partnerUserID here even though it could have been
+        // deleted optimistically because if the deletion is pending, do want to show
+        // the option to add a new phone or email login, so we don't want to find
+        // that login type in the list here.
+        if (Str.isValidPhone(Str.removeSMSDomain(login.partnerUserID))) {
+            hasPhoneNumberLogin = true;
+        } else if (Str.isValidEmail(login.partnerUserID)) {
+            hasEmailLogin = true;
         }
 
-        // eslint-disable-next-line react/no-did-update-set-state
-        this.setState(stateToUpdate);
-    }
-
-    /**
-     * Get the most validated login of each type
-     *
-     * @returns {Object}
-     */
-    getLogins() {
-        return _.reduce(_.values(this.props.loginList), (logins, currentLogin) => {
-            const type = Str.isSMSLogin(currentLogin.partnerUserID) ? CONST.LOGIN_TYPE.PHONE : CONST.LOGIN_TYPE.EMAIL;
-            const login = Str.removeSMSDomain(currentLogin.partnerUserID);
-
-            // If there's already a login type that's validated and/or currentLogin isn't valid then return early
-            if ((login !== lodashGet(this.props.currentUserPersonalDetails, 'login')) && !_.isEmpty(logins[type])
-                && (logins[type].validatedDate || !currentLogin.validatedDate)) {
-                return logins;
-            }
-            return {
-                ...logins,
-                [type]: {
-                    ...currentLogin,
-                    type,
-                    partnerUserID: Str.removeSMSDomain(currentLogin.partnerUserID),
-                },
-            };
-        }, {
-            phone: {},
-            email: {},
-        });
-    }
-
-    render() {
+        // Default to using login key if we deleted login.partnerUserID optimistically
+        // but still need to show the pending login being deleted while offline.
+        const partnerUserID = login.partnerUserID || loginName;
         return (
-            <ScreenWrapper>
-                <HeaderWithCloseButton
-                    title={this.props.translate('contacts.contactMethods')}
-                    shouldShowBackButton
-                    onBackButtonPress={() => Navigation.navigate(ROUTES.SETTINGS_PROFILE)}
-                    onCloseButtonPress={() => Navigation.dismissModal(true)}
+            <OfflineWithFeedback
+                pendingAction={pendingAction}
+                key={partnerUserID}
+            >
+                <MenuItem
+                    title={Str.removeSMSDomain(partnerUserID)}
+                    description={description}
+                    onPress={() => Navigation.navigate(ROUTES.getEditContactMethodRoute(partnerUserID))}
+                    brickRoadIndicator={indicator}
+                    shouldShowBasicTitle
+                    shouldShowRightIcon
+                    disabled={!_.isEmpty(pendingAction)}
                 />
-                <ScrollView>
+            </OfflineWithFeedback>
+        );
+    });
+
+    return (
+        <ScreenWrapper>
+            <HeaderWithCloseButton
+                title={props.translate('contacts.contactMethods')}
+                shouldShowBackButton
+                onBackButtonPress={() => Navigation.navigate(ROUTES.SETTINGS_PROFILE)}
+                onCloseButtonPress={() => Navigation.dismissModal(true)}
+            />
+            <ScrollView>
+                <View style={[styles.ph5, styles.mv3, styles.flexRow, styles.flexWrap]}>
+                    <Text>
+                        {props.translate('contacts.helpTextBeforeEmail')}
+                        <CopyTextToClipboard
+                            text="receipts@expensify.com"
+                            textStyles={[styles.textBlue]}
+                        />
+                        <Text>{props.translate('contacts.helpTextAfterEmail')}</Text>
+                    </Text>
+                </View>
+                {loginMenuItems}
+                {/* The below fields will be removed soon, when we implement the new Add Contact Method page */}
+                {!hasEmailLogin && (
                     <LoginField
-                        label={this.props.translate('profilePage.emailAddress')}
+                        label={props.translate('profilePage.emailAddress')}
                         type="email"
-                        login={this.state.logins.email}
-                        defaultValue={this.state.logins.email}
-                    />
-                    <LoginField
-                        label={this.props.translate('common.phoneNumber')}
-                        type="phone"
-                        login={this.state.logins.phone}
-                        defaultValue={this.state.logins.phone}
+                        login={{}}
                     />
-                </ScrollView>
-            </ScreenWrapper>
-        );
-    }
-}
+                )}
+            </ScrollView>
+        </ScreenWrapper>
+    );
+};
 
 ContactMethodsPage.propTypes = propTypes;
 ContactMethodsPage.defaultProps = defaultProps;
+ContactMethodsPage.displayName = 'ContactMethodsPage';
 
 export default compose(
     withLocalize,
-    withCurrentUserPersonalDetails,
     withOnyx({
         loginList: {
             key: ONYXKEYS.LOGIN_LIST,
         },
+        session: {
+            key: ONYXKEYS.SESSION,
+        },
     }),
 )(ContactMethodsPage);
diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js
index 6672198dc7db..dc43443bc4fd 100755
--- a/src/pages/settings/Profile/ProfilePage.js
+++ b/src/pages/settings/Profile/ProfilePage.js
@@ -2,6 +2,8 @@ import Str from 'expensify-common/lib/str';
 import lodashGet from 'lodash/get';
 import React from 'react';
 import {View} from 'react-native';
+import PropTypes from 'prop-types';
+import {withOnyx} from 'react-native-onyx';
 import {ScrollView} from 'react-native-gesture-handler';
 import _ from 'underscore';
 import AvatarWithImagePicker from '../../../components/AvatarWithImagePicker';
@@ -20,8 +22,21 @@ import * as ReportUtils from '../../../libs/ReportUtils';
 import ROUTES from '../../../ROUTES';
 import styles from '../../../styles/styles';
 import * as Expensicons from '../../../components/Icon/Expensicons';
+import ONYXKEYS from '../../../ONYXKEYS';
+import * as UserUtils from '../../../libs/UserUtils';
 
 const propTypes = {
+    /* Onyx Props */
+
+    /** Login list for the user that is signed in */
+    loginList: PropTypes.shape({
+        /** Date login was validated, used to show brickroad info status */
+        validatedDate: PropTypes.string,
+
+        /** Field-specific server side errors keyed by microtime */
+        errorFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)),
+    }),
+
     ...withLocalizePropTypes,
     ...withCurrentUserPersonalDetailsPropTypes,
 };
@@ -39,6 +54,8 @@ const ProfilePage = (props) => {
         return lodashGet(props.translate('pronouns'), pronounsKey, props.translate('profilePage.selectYourPronouns'));
     };
     const currentUserDetails = props.currentUserPersonalDetails || {};
+    const contactMethodBrickRoadIndicator = UserUtils.getLoginListBrickRoadIndicator(props.loginList);
+
     const profileSettingsOptions = [
         {
             description: props.translate('displayNamePage.headerTitle'),
@@ -49,6 +66,7 @@ const ProfilePage = (props) => {
             description: props.translate('contacts.contactMethod'),
             title: Str.removeSMSDomain(lodashGet(currentUserDetails, 'login', '')),
             pageRoute: ROUTES.SETTINGS_CONTACT_METHODS,
+            brickRoadIndicator: contactMethodBrickRoadIndicator,
         },
         {
             description: props.translate('pronounsPage.pronouns'),
@@ -94,6 +112,7 @@ const ProfilePage = (props) => {
                             title={detail.title}
                             description={detail.description}
                             onPress={() => Navigation.navigate(detail.pageRoute)}
+                            brickRoadIndicator={detail.brickRoadIndicator}
                         />
                     ))}
                 </View>
@@ -115,4 +134,9 @@ ProfilePage.displayName = 'ProfilePage';
 export default compose(
     withLocalize,
     withCurrentUserPersonalDetails,
+    withOnyx({
+        loginList: {
+            key: ONYXKEYS.LOGIN_LIST,
+        },
+    }),
 )(ProfilePage);
diff --git a/src/styles/styles.js b/src/styles/styles.js
index 995218a2ae83..34e06513edd0 100644
--- a/src/styles/styles.js
+++ b/src/styles/styles.js
@@ -1003,31 +1003,18 @@ const styles = {
         width: variables.componentSizeNormal,
     },
 
-    statusIndicator: {
+    statusIndicator: (backgroundColor = themeColors.danger) => ({
         borderColor: themeColors.sidebar,
-        backgroundColor: themeColors.danger,
-        borderRadius: 6,
-        borderWidth: 2,
-        position: 'absolute',
-        right: -1,
-        bottom: -1,
-        height: 12,
-        width: 12,
-        zIndex: 10,
-    },
-
-    statusIndicatorLarge: {
-        borderColor: themeColors.componentBG,
-        backgroundColor: themeColors.danger,
+        backgroundColor,
         borderRadius: 8,
         borderWidth: 2,
         position: 'absolute',
-        right: 4,
-        bottom: 4,
+        right: -2,
+        top: -1,
         height: 16,
         width: 16,
         zIndex: 10,
-    },
+    }),
 
     statusIndicatorOnline: {
         backgroundColor: themeColors.success,
diff --git a/src/styles/utilities/spacing.js b/src/styles/utilities/spacing.js
index 2283bd63d046..bf21efb012d1 100644
--- a/src/styles/utilities/spacing.js
+++ b/src/styles/utilities/spacing.js
@@ -201,6 +201,10 @@ export default {
         marginBottom: 24,
     },
 
+    mb7: {
+        marginBottom: 28,
+    },
+
     mb8: {
         marginBottom: 32,
     },

From 05274b251a3d8be4a07b996111bb7899ddc269d5 Mon Sep 17 00:00:00 2001
From: Alex Beaman <alexbeaman@expensify.com>
Date: Fri, 10 Mar 2023 12:20:28 +0200
Subject: [PATCH 04/28] Fix lint and missing LoginField

---
 src/pages/settings/Profile/Contacts/ContactMethodsPage.js | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/src/pages/settings/Profile/Contacts/ContactMethodsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodsPage.js
index 63645f0e54ee..10e0eafef8e6 100644
--- a/src/pages/settings/Profile/Contacts/ContactMethodsPage.js
+++ b/src/pages/settings/Profile/Contacts/ContactMethodsPage.js
@@ -28,6 +28,7 @@ const propTypes = {
     loginList: PropTypes.shape({
         /** Value of partner name */
         partnerName: PropTypes.string,
+
         /** Phone/Email associated with user */
         partnerUserID: PropTypes.string,
 
@@ -138,6 +139,13 @@ const ContactMethodsPage = (props) => {
                         login={{}}
                     />
                 )}
+                {!hasPhoneNumberLogin && (
+                    <LoginField
+                        label={props.translate('common.phoneNumber')}
+                        type="phone"
+                        login={{}}
+                    />
+                )}
             </ScrollView>
         </ScreenWrapper>
     );

From 17fd385155a696271bd609acd37b720e65fb3515 Mon Sep 17 00:00:00 2001
From: Alex Beaman <alexbeaman@expensify.com>
Date: Fri, 10 Mar 2023 16:19:09 +0200
Subject: [PATCH 05/28] A bit of cleanup

---
 src/libs/actions/User.js                               |  1 +
 .../Profile/Contacts/ContactMethodDetailsPage.js       | 10 ++--------
 2 files changed, 3 insertions(+), 8 deletions(-)

diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js
index 01daca6f7a28..1c9a296c975e 100644
--- a/src/libs/actions/User.js
+++ b/src/libs/actions/User.js
@@ -268,6 +268,7 @@ function deleteContactMethod(contactMethod, oldLoginData) {
     API.write('DeleteContactMethod', {
         partnerUserID: contactMethod,
     }, {optimisticData, successData, failureData});
+    Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS);
 }
 
 /**
diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
index 4996f55dfd2a..f325daf364a1 100644
--- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
+++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
@@ -119,11 +119,7 @@ class ContactMethodDetailsPage extends Component {
      */
     doesContactMethodExist() {
         const contactMethod = this.getContactMethod();
-        const loginData = this.props.loginList[contactMethod];
-        if (!contactMethod || !loginData) {
-            return false;
-        }
-        return true;
+        return contactMethod && this.props.loginList[contactMethod];
     }
 
     /**
@@ -138,10 +134,8 @@ class ContactMethodDetailsPage extends Component {
      * Delete the contact method and hide the modal
      */
     confirmDeleteAndHideModal() {
-        const contactMethod = this.getContactMethod();
-        User.deleteContactMethod(contactMethod);
         this.toggleDeleteModal(false);
-        Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS);
+        User.deleteContactMethod(this.getContactMethod());
     }
 
     /**

From b659e1bbca2d03002f4b01cc96f716ce96b31d7a Mon Sep 17 00:00:00 2001
From: Aldo Canepa <aldo@expensify.com>
Date: Thu, 16 Mar 2023 10:31:40 -0700
Subject: [PATCH 06/28] Remove Navigation.isNavigating

---
 src/components/ScreenWrapper/index.js |  9 ---------
 src/libs/Navigation/Navigation.js     | 19 +------------------
 2 files changed, 1 insertion(+), 27 deletions(-)

diff --git a/src/components/ScreenWrapper/index.js b/src/components/ScreenWrapper/index.js
index 5c83dda89619..e47e101c3ea7 100644
--- a/src/components/ScreenWrapper/index.js
+++ b/src/components/ScreenWrapper/index.js
@@ -38,13 +38,7 @@ class ScreenWrapper extends React.Component {
             Navigation.dismissModal();
         }, shortcutConfig.descriptionKey, shortcutConfig.modifiers, true);
 
-        this.unsubscribeTransitionStart = this.props.navigation.addListener('transitionStart', () => {
-            Navigation.setIsNavigating(true);
-        });
-
         this.unsubscribeTransitionEnd = this.props.navigation.addListener('transitionEnd', (event) => {
-            Navigation.setIsNavigating(false);
-
             // Prevent firing the prop callback when user is exiting the page.
             if (lodashGet(event, 'data.closing')) {
                 return;
@@ -85,9 +79,6 @@ class ScreenWrapper extends React.Component {
         if (this.unsubscribeTransitionEnd) {
             this.unsubscribeTransitionEnd();
         }
-        if (this.unsubscribeTransitionStart) {
-            this.unsubscribeTransitionStart();
-        }
         if (this.beforeRemoveSubscription) {
             this.beforeRemoveSubscription();
         }
diff --git a/src/libs/Navigation/Navigation.js b/src/libs/Navigation/Navigation.js
index 6c3dcfa618b4..7d8cc8d07609 100644
--- a/src/libs/Navigation/Navigation.js
+++ b/src/libs/Navigation/Navigation.js
@@ -29,7 +29,6 @@ const reportScreenIsReadyPromise = new Promise((resolve) => {
 
 let isLoggedIn = false;
 let pendingRoute = null;
-let isNavigating = false;
 
 Onyx.connect({
     key: ONYXKEYS.SESSION,
@@ -55,27 +54,13 @@ function setDidTapNotification() {
  * @returns {Boolean}
  */
 function canNavigate(methodName, params = {}) {
-    if (navigationRef.isReady() && !isNavigating) {
+    if (navigationRef.isReady()) {
         return true;
     }
-
-    if (isNavigating) {
-        Log.hmmm(`[Navigation] ${methodName} failed because navigation is progress`, params);
-        return false;
-    }
-
     Log.hmmm(`[Navigation] ${methodName} failed because navigation ref was not yet ready`, params);
     return false;
 }
 
-/**
- * Sets Navigation State
- * @param {Boolean} isNavigatingValue
- */
-function setIsNavigating(isNavigatingValue) {
-    isNavigating = isNavigatingValue;
-}
-
 /**
  * Opens the LHN drawer.
  * @private
@@ -128,7 +113,6 @@ function goBack(shouldOpenDrawer = true) {
         }
         return;
     }
-
     navigationRef.current.goBack();
 }
 
@@ -309,7 +293,6 @@ export default {
     setIsDrawerReady,
     resetDrawerIsReadyPromise,
     isDrawerRoute,
-    setIsNavigating,
     isReportScreenReady,
     setIsReportScreenIsReady,
 };

From 109479b02e764aaad6f1bd386c834313efda8aa2 Mon Sep 17 00:00:00 2001
From: Aldo Canepa <aldo@expensify.com>
Date: Tue, 21 Mar 2023 00:16:53 -0700
Subject: [PATCH 07/28] Polish

---
 .../settings/Profile/Contacts/ContactMethodDetailsPage.js | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
index f325daf364a1..15122c3f7a9d 100644
--- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
+++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
@@ -124,10 +124,10 @@ class ContactMethodDetailsPage extends Component {
 
     /**
      * Toggle delete confirm modal visibility
-     * @param {Boolean} shouldOpen
+     * @param {Boolean} isOpen
      */
-    toggleDeleteModal(shouldOpen) {
-        this.setState({isDeleteModalOpen: shouldOpen});
+    toggleDeleteModal(isOpen) {
+        this.setState({isDeleteModalOpen: isOpen});
     }
 
     /**
@@ -159,7 +159,7 @@ class ContactMethodDetailsPage extends Component {
             return <FullScreenLoadingIndicator />;
         }
 
-        const isDefaultContactMethod = (this.props.session.email === loginData.partnerUserID);
+        const isDefaultContactMethod = this.props.session.email === loginData.partnerUserID;
         const hasMagicCodeBeenSent = lodashGet(this.props.loginList, [contactMethod, 'validateCodeSent'], false);
 
         return (

From e0354dfdf2971f436016e30f080818f09c4f42a8 Mon Sep 17 00:00:00 2001
From: Aldo Canepa <aldo@expensify.com>
Date: Tue, 21 Mar 2023 12:39:11 -0700
Subject: [PATCH 08/28] Show NOT FOUND when contact method is missing

---
 .../Contacts/ContactMethodDetailsPage.js      | 28 ++-----------------
 1 file changed, 2 insertions(+), 26 deletions(-)

diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
index 15122c3f7a9d..471d3999b010 100644
--- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
+++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
@@ -25,7 +25,7 @@ import colors from '../../../../styles/colors';
 import Button from '../../../../components/Button';
 import * as ErrorUtils from '../../../../libs/ErrorUtils';
 import themeColors from '../../../../styles/themes/default';
-import FullScreenLoadingIndicator from '../../../../components/FullscreenLoadingIndicator';
+import NotFoundPage from '../../../ErrorPage/NotFoundPage';
 
 const propTypes = {
     /* Onyx Props */
@@ -89,20 +89,6 @@ class ContactMethodDetailsPage extends Component {
         };
     }
 
-    componentDidMount() {
-        if (this.doesContactMethodExist()) {
-            return;
-        }
-        Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS);
-    }
-
-    componentDidUpdate() {
-        if (this.doesContactMethodExist()) {
-            return;
-        }
-        Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS);
-    }
-
     /**
      * Gets the current contact method from the route params
      *
@@ -112,16 +98,6 @@ class ContactMethodDetailsPage extends Component {
         return decodeURIComponent(lodashGet(this.props.route, 'params.contactMethod'));
     }
 
-    /**
-     * Checks that we have a contact method in the route params and that we can find the login data in Onyx
-     *
-     * @returns {boolean}
-     */
-    doesContactMethodExist() {
-        const contactMethod = this.getContactMethod();
-        return contactMethod && this.props.loginList[contactMethod];
-    }
-
     /**
      * Toggle delete confirm modal visibility
      * @param {Boolean} isOpen
@@ -156,7 +132,7 @@ class ContactMethodDetailsPage extends Component {
         const contactMethod = this.getContactMethod();
         const loginData = this.props.loginList[contactMethod];
         if (!contactMethod || !loginData) {
-            return <FullScreenLoadingIndicator />;
+            return <NotFoundPage />;
         }
 
         const isDefaultContactMethod = this.props.session.email === loginData.partnerUserID;

From 6420459db3b6b5f43592c834b52131d531567504 Mon Sep 17 00:00:00 2001
From: Aldo Canepa <aldo@expensify.com>
Date: Tue, 21 Mar 2023 12:39:25 -0700
Subject: [PATCH 09/28] Update comments

---
 src/pages/settings/Profile/Contacts/ContactMethodsPage.js | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/src/pages/settings/Profile/Contacts/ContactMethodsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodsPage.js
index 10e0eafef8e6..23731da297e5 100644
--- a/src/pages/settings/Profile/Contacts/ContactMethodsPage.js
+++ b/src/pages/settings/Profile/Contacts/ContactMethodsPage.js
@@ -26,14 +26,13 @@ const propTypes = {
 
     /** Login list for the user that is signed in */
     loginList: PropTypes.shape({
-        /** Value of partner name */
+        /** The partner creating the account. It depends on the source: website, mobile, integrations, ... */
         partnerName: PropTypes.string,
 
         /** Phone/Email associated with user */
         partnerUserID: PropTypes.string,
 
-        /** Date of when login was validated */
-        /** Date login was validated, used to show brickroad info status */
+        /** The date when the login was validated, used to show the brickroad status */
         validatedDate: PropTypes.string,
 
         /** Field-specific server side errors keyed by microtime */
@@ -81,7 +80,7 @@ const ContactMethodsPage = (props) => {
         // Temporary checks to determine if we need to show specific LoginField
         // components. This check will be removed soon.
         // Also we still use login.partnerUserID here even though it could have been
-        // deleted optimistically because if the deletion is pending, do want to show
+        // deleted optimistically because if the deletion is pending, we want to show
         // the option to add a new phone or email login, so we don't want to find
         // that login type in the list here.
         if (Str.isValidPhone(Str.removeSMSDomain(login.partnerUserID))) {

From 6e0de6243ae1ac64f623cf39df733e252c1abb34 Mon Sep 17 00:00:00 2001
From: Aldo Canepa <aldo@expensify.com>
Date: Tue, 21 Mar 2023 12:55:40 -0700
Subject: [PATCH 10/28] Update comment about follow up PR

---
 src/pages/settings/Profile/Contacts/ContactMethodsPage.js | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/pages/settings/Profile/Contacts/ContactMethodsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodsPage.js
index 23731da297e5..45efe531bd98 100644
--- a/src/pages/settings/Profile/Contacts/ContactMethodsPage.js
+++ b/src/pages/settings/Profile/Contacts/ContactMethodsPage.js
@@ -78,7 +78,8 @@ const ContactMethodsPage = (props) => {
         }
 
         // Temporary checks to determine if we need to show specific LoginField
-        // components. This check will be removed soon.
+        // components. This check will be removed soon by this follow up PR:
+        // https://github.com/Expensify/App/pull/15330
         // Also we still use login.partnerUserID here even though it could have been
         // deleted optimistically because if the deletion is pending, we want to show
         // the option to add a new phone or email login, so we don't want to find

From c2931dae1ae5c5a9fab25432a012582c614b08af Mon Sep 17 00:00:00 2001
From: Aldo Canepa <aldo@expensify.com>
Date: Tue, 21 Mar 2023 16:34:11 -0700
Subject: [PATCH 11/28] Remove unused prop size

---
 src/components/AvatarWithIndicator.js | 9 +--------
 1 file changed, 1 insertion(+), 8 deletions(-)

diff --git a/src/components/AvatarWithIndicator.js b/src/components/AvatarWithIndicator.js
index a53d7e503bed..93d6e3d82a73 100644
--- a/src/components/AvatarWithIndicator.js
+++ b/src/components/AvatarWithIndicator.js
@@ -23,9 +23,6 @@ const propTypes = {
     /** URL for the avatar */
     source: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
 
-    /** Avatar size */
-    size: PropTypes.string,
-
     /** To show a tooltip on hover */
     tooltipText: PropTypes.string,
 
@@ -60,7 +57,6 @@ const propTypes = {
 };
 
 const defaultProps = {
-    size: 'default',
     tooltipText: '',
     policiesMemberList: {},
     policies: {},
@@ -107,10 +103,7 @@ const AvatarWithIndicator = (props) => {
     return (
         <View style={[styles.sidebarAvatar]}>
             <Tooltip text={props.tooltipText}>
-                <Avatar
-                    source={ReportUtils.getSmallSizeAvatar(props.source)}
-                    size={props.size}
-                />
+                <Avatar source={ReportUtils.getSmallSizeAvatar(props.source)} />
                 {(shouldShowErrorIndicator || shouldShowInfoIndicator) && (
                     <View style={StyleSheet.flatten(indicatorStyles)} />
                 )}

From 1d7502be94c71adfd3101e458933f8f324705325 Mon Sep 17 00:00:00 2001
From: Aldo Canepa Garay <87341702+aldo-expensify@users.noreply.github.com>
Date: Tue, 21 Mar 2023 16:35:34 -0700
Subject: [PATCH 12/28] Update comment

Co-authored-by: Rajat Parashar <parasharrajat@users.noreply.github.com>
---
 src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
index 471d3999b010..2ea46e527a31 100644
--- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
+++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
@@ -56,7 +56,7 @@ const propTypes = {
     /** Route params */
     route: PropTypes.shape({
         params: PropTypes.shape({
-            /** passed via route /settings/profile/contact-methods/:contactMethod/details */
+            /** Passed via route /settings/profile/contact-methods/:contactMethod/details */
             contactMethod: PropTypes.string,
         }),
     }),

From 5eb315a225f4e1ea27bdc51ce1a86fc2698cd637 Mon Sep 17 00:00:00 2001
From: Aldo Canepa <aldo@expensify.com>
Date: Thu, 23 Mar 2023 11:26:34 -0700
Subject: [PATCH 13/28] Fix margins

---
 .../settings/Profile/Contacts/AddSecondaryLoginPage.js      | 2 +-
 .../settings/Profile/Contacts/ContactMethodDetailsPage.js   | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/pages/settings/Profile/Contacts/AddSecondaryLoginPage.js b/src/pages/settings/Profile/Contacts/AddSecondaryLoginPage.js
index 36571a14d4a6..823d13d952df 100755
--- a/src/pages/settings/Profile/Contacts/AddSecondaryLoginPage.js
+++ b/src/pages/settings/Profile/Contacts/AddSecondaryLoginPage.js
@@ -117,7 +117,7 @@ class AddSecondaryLoginPage extends Component {
                     onCloseButtonPress={() => Navigation.dismissModal()}
                 />
                 {/* We use keyboardShouldPersistTaps="handled" to prevent the keyboard from being hidden when switching focus on input fields  */}
-                <ScrollView style={styles.flex1} contentContainerStyle={styles.p5} keyboardShouldPersistTaps="handled">
+                <ScrollView style={styles.flex1} contentContainerStyle={[styles.ph5, styles.mv3]} keyboardShouldPersistTaps="handled">
                     <Text style={[styles.mb6]}>
                         {this.props.translate(this.formType === CONST.LOGIN_TYPE.PHONE
                             ? 'addSecondaryLoginPage.enterPreferredPhoneNumberToSendValidationLink'
diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
index 2ea46e527a31..e288fc3769ae 100644
--- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
+++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
@@ -157,8 +157,8 @@ class ContactMethodDetailsPage extends Component {
                         danger
                     />
                     {!loginData.validatedDate && (
-                        <View style={[styles.mh5, styles.mb7]}>
-                            <View style={[styles.flexRow, styles.alignItemsCenter, styles.mb1, styles.mt3]}>
+                        <View style={[styles.ph5, styles.mt3, styles.mb7]}>
+                            <View style={[styles.flexRow, styles.alignItemsCenter, styles.mb1]}>
                                 <Icon src={Expensicons.DotIndicator} fill={colors.green} />
                                 <View style={[styles.flex1, styles.ml4]}>
                                     <Text>
@@ -208,7 +208,7 @@ class ContactMethodDetailsPage extends Component {
                         </View>
                     )}
                     {isDefaultContactMethod ? (
-                        <Text style={[styles.ph5]}>
+                        <Text style={[styles.ph5, styles.mv3]}>
                             {this.props.translate('contacts.yourDefaultContactMethod')}
                         </Text>
                     ) : (

From 2d7094c7e142d6e79fa4e601352970cd54b360ac Mon Sep 17 00:00:00 2001
From: Aldo Canepa <aldo@expensify.com>
Date: Thu, 23 Mar 2023 11:27:39 -0700
Subject: [PATCH 14/28] Use CONST

---
 src/pages/settings/Profile/Contacts/ContactMethodsPage.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/pages/settings/Profile/Contacts/ContactMethodsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodsPage.js
index 45efe531bd98..0a4ddcab719f 100644
--- a/src/pages/settings/Profile/Contacts/ContactMethodsPage.js
+++ b/src/pages/settings/Profile/Contacts/ContactMethodsPage.js
@@ -135,14 +135,14 @@ const ContactMethodsPage = (props) => {
                 {!hasEmailLogin && (
                     <LoginField
                         label={props.translate('profilePage.emailAddress')}
-                        type="email"
+                        type={CONST.LOGIN_TYPE.EMAIL}
                         login={{}}
                     />
                 )}
                 {!hasPhoneNumberLogin && (
                     <LoginField
                         label={props.translate('common.phoneNumber')}
-                        type="phone"
+                        type={CONST.LOGIN_TYPE.PHONE}
                         login={{}}
                     />
                 )}

From 1aed80b78e314b7c0932f7ca4a4bdec27a779ba5 Mon Sep 17 00:00:00 2001
From: Aldo Canepa <aldo@expensify.com>
Date: Thu, 23 Mar 2023 11:33:54 -0700
Subject: [PATCH 15/28] Submit validation code on press enter

---
 src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
index e288fc3769ae..408e74e769e5 100644
--- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
+++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
@@ -203,6 +203,7 @@ class ContactMethodDetailsPage extends Component {
                                     onPress={this.validateContactMethod}
                                     style={[styles.mt4]}
                                     success
+                                    pressOnEnter
                                 />
                             </OfflineWithFeedback>
                         </View>

From 4f52414f689e418bd5d3321a60433bb55d684c7c Mon Sep 17 00:00:00 2001
From: Aldo Canepa <aldo@expensify.com>
Date: Thu, 23 Mar 2023 11:34:04 -0700
Subject: [PATCH 16/28] Restrict autocomplete to phones only

---
 src/pages/settings/Profile/Contacts/AddSecondaryLoginPage.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/pages/settings/Profile/Contacts/AddSecondaryLoginPage.js b/src/pages/settings/Profile/Contacts/AddSecondaryLoginPage.js
index 823d13d952df..220d5b07d3bf 100755
--- a/src/pages/settings/Profile/Contacts/AddSecondaryLoginPage.js
+++ b/src/pages/settings/Profile/Contacts/AddSecondaryLoginPage.js
@@ -134,6 +134,7 @@ class AddSecondaryLoginPage extends Component {
                             keyboardType={this.formType === CONST.LOGIN_TYPE.PHONE
                                 ? CONST.KEYBOARD_TYPE.PHONE_PAD : CONST.KEYBOARD_TYPE.EMAIL_ADDRESS}
                             returnKeyType="done"
+                            autoComplete="tel"
                         />
                     </View>
                     {!Permissions.canUsePasswordlessLogins(this.props.betas) && (

From 21f66237a0eedbc0fedd01d75d8598dd48ea9ee7 Mon Sep 17 00:00:00 2001
From: Aldo Canepa <aldo@expensify.com>
Date: Thu, 23 Mar 2023 11:36:01 -0700
Subject: [PATCH 17/28] Remove unused css

---
 src/styles/styles.js | 18 ------------------
 1 file changed, 18 deletions(-)

diff --git a/src/styles/styles.js b/src/styles/styles.js
index b92d89d367bf..4069ed2d300a 100644
--- a/src/styles/styles.js
+++ b/src/styles/styles.js
@@ -1075,24 +1075,6 @@ const styles = {
         zIndex: 10,
     }),
 
-    statusIndicatorOnline: {
-        backgroundColor: themeColors.success,
-    },
-
-    avatarWithIndicator: {
-        errorDot: {
-            borderColor: themeColors.sidebar,
-            borderRadius: 6,
-            borderWidth: 2,
-            position: 'absolute',
-            right: -1,
-            bottom: -1,
-            height: 12,
-            width: 12,
-            zIndex: 10,
-        },
-    },
-
     floatingActionButtonContainer: {
         position: 'absolute',
         right: 20,

From 634ed14369fa0be58c08cec52ec1a11c7dfb2a5b Mon Sep 17 00:00:00 2001
From: Aldo Canepa <aldo@expensify.com>
Date: Thu, 23 Mar 2023 12:18:11 -0700
Subject: [PATCH 18/28] Remove required from prop types / add defaults

---
 src/pages/settings/InitialSettingsPage.js                    | 1 +
 .../settings/Profile/Contacts/ContactMethodDetailsPage.js    | 5 ++++-
 src/pages/settings/Profile/Contacts/ContactMethodsPage.js    | 5 ++++-
 src/pages/settings/Profile/ProfilePage.js                    | 1 +
 4 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js
index 9d153d8728b5..0593c10e4fb6 100755
--- a/src/pages/settings/InitialSettingsPage.js
+++ b/src/pages/settings/InitialSettingsPage.js
@@ -108,6 +108,7 @@ const defaultProps = {
     walletTerms: {},
     bankAccountList: {},
     cardList: {},
+    loginList: {},
     policyMembers: {},
     ...withCurrentUserPersonalDetailsDefaultProps,
 };
diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
index 408e74e769e5..7ae502fc8e1d 100644
--- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
+++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
@@ -51,7 +51,7 @@ const propTypes = {
     /** Current user session */
     session: PropTypes.shape({
         email: PropTypes.string.isRequired,
-    }).isRequired,
+    }),
 
     /** Route params */
     route: PropTypes.shape({
@@ -66,6 +66,9 @@ const propTypes = {
 
 const defaultProps = {
     loginList: {},
+    session: {
+        email: null,
+    },
     route: {
         params: {
             contactMethod: '',
diff --git a/src/pages/settings/Profile/Contacts/ContactMethodsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodsPage.js
index 0a4ddcab719f..a205eb7da709 100644
--- a/src/pages/settings/Profile/Contacts/ContactMethodsPage.js
+++ b/src/pages/settings/Profile/Contacts/ContactMethodsPage.js
@@ -45,13 +45,16 @@ const propTypes = {
     /** Current user session */
     session: PropTypes.shape({
         email: PropTypes.string.isRequired,
-    }).isRequired,
+    }),
 
     ...withLocalizePropTypes,
 };
 
 const defaultProps = {
     loginList: {},
+    session: {
+        email: null,
+    },
 };
 
 const ContactMethodsPage = (props) => {
diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js
index dc43443bc4fd..3ea4f9abb9f7 100755
--- a/src/pages/settings/Profile/ProfilePage.js
+++ b/src/pages/settings/Profile/ProfilePage.js
@@ -42,6 +42,7 @@ const propTypes = {
 };
 
 const defaultProps = {
+    loginList: {},
     ...withCurrentUserPersonalDetailsDefaultProps,
 };
 

From 012faeb278530adb2277dbf41e6b60f2e67731ec Mon Sep 17 00:00:00 2001
From: Aldo Canepa <aldo@expensify.com>
Date: Thu, 23 Mar 2023 16:07:16 -0700
Subject: [PATCH 19/28] Pass correct autoComplete value depending on type

---
 src/pages/settings/Profile/Contacts/AddSecondaryLoginPage.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/pages/settings/Profile/Contacts/AddSecondaryLoginPage.js b/src/pages/settings/Profile/Contacts/AddSecondaryLoginPage.js
index 220d5b07d3bf..48232251df5b 100755
--- a/src/pages/settings/Profile/Contacts/AddSecondaryLoginPage.js
+++ b/src/pages/settings/Profile/Contacts/AddSecondaryLoginPage.js
@@ -134,7 +134,7 @@ class AddSecondaryLoginPage extends Component {
                             keyboardType={this.formType === CONST.LOGIN_TYPE.PHONE
                                 ? CONST.KEYBOARD_TYPE.PHONE_PAD : CONST.KEYBOARD_TYPE.EMAIL_ADDRESS}
                             returnKeyType="done"
-                            autoComplete="tel"
+                            autoComplete={this.formType === CONST.LOGIN_TYPE.PHONE ? 'tel' : 'email'}
                         />
                     </View>
                     {!Permissions.canUsePasswordlessLogins(this.props.betas) && (

From fc31a8dd0c88a1e8c22afa6d281669207ece564c Mon Sep 17 00:00:00 2001
From: Alex Beaman <alexbeaman@expensify.com>
Date: Mon, 27 Mar 2023 16:56:03 +0000
Subject: [PATCH 20/28] Add new validation to magic code input

---
 .../Contacts/ContactMethodDetailsPage.js        | 17 +++++++++++++----
 1 file changed, 13 insertions(+), 4 deletions(-)

diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
index 7ae502fc8e1d..9bb6262efb69 100644
--- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
+++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
@@ -84,9 +84,10 @@ class ContactMethodDetailsPage extends Component {
         this.confirmDeleteAndHideModal = this.confirmDeleteAndHideModal.bind(this);
         this.resendValidateCode = this.resendValidateCode.bind(this);
         this.getContactMethod = this.getContactMethod.bind(this);
-        this.validateContactMethod = this.validateContactMethod.bind(this);
+        this.validateAndSubmitCode = this.validateAndSubmitCode.bind(this);
 
         this.state = {
+            formError: '',
             isDeleteModalOpen: false,
             validateCode: '',
         };
@@ -127,8 +128,14 @@ class ContactMethodDetailsPage extends Component {
     /**
      * Attempt to validate this contact method
      */
-    validateContactMethod() {
-        User.validateSecondaryLogin(this.getContactMethod(), this.state.validateCode);
+    validateAndSubmitCode() {
+        if (!this.state.validateCode) {
+            this.setState({formError: 'validateCodeForm.error.pleaseFillMagicCode'});
+        } else if (this.state.validateCode.length !== 6) {
+            this.setState({formError: 'validateCodeForm.error.incorrectMagicCode'});
+        } else {
+            User.validateSecondaryLogin(this.getContactMethod(), this.state.validateCode);
+        }
     }
 
     render() {
@@ -140,6 +147,7 @@ class ContactMethodDetailsPage extends Component {
 
         const isDefaultContactMethod = this.props.session.email === loginData.partnerUserID;
         const hasMagicCodeBeenSent = lodashGet(this.props.loginList, [contactMethod, 'validateCodeSent'], false);
+        const formErrorText = this.state.formError ? this.props.translate(this.state.formError) : '';
 
         return (
             <ScreenWrapper>
@@ -176,6 +184,7 @@ class ContactMethodDetailsPage extends Component {
                                 onChangeText={text => this.setState({validateCode: text})}
                                 keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD}
                                 blurOnSubmit={false}
+                                errorText={formErrorText}
                             />
                             <OfflineWithFeedback
                                 pendingAction={lodashGet(loginData, 'pendingFields.validateCodeSent', null)}
@@ -203,7 +212,7 @@ class ContactMethodDetailsPage extends Component {
                             >
                                 <Button
                                     text={this.props.translate('common.verify')}
-                                    onPress={this.validateContactMethod}
+                                    onPress={this.validateAndSubmitCode}
                                     style={[styles.mt4]}
                                     success
                                     pressOnEnter

From 322e72aa453e7bbad78e77085e81f15d1a0e4831 Mon Sep 17 00:00:00 2001
From: Alex Beaman <alexbeaman@expensify.com>
Date: Mon, 27 Mar 2023 16:59:05 +0000
Subject: [PATCH 21/28] Reuse existing validate code validation

---
 .../settings/Profile/Contacts/ContactMethodDetailsPage.js      | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
index 9bb6262efb69..ff88d25c3aed 100644
--- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
+++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
@@ -26,6 +26,7 @@ import Button from '../../../../components/Button';
 import * as ErrorUtils from '../../../../libs/ErrorUtils';
 import themeColors from '../../../../styles/themes/default';
 import NotFoundPage from '../../../ErrorPage/NotFoundPage';
+import * as ValidationUtils from '../../../../libs/ValidationUtils'
 
 const propTypes = {
     /* Onyx Props */
@@ -131,7 +132,7 @@ class ContactMethodDetailsPage extends Component {
     validateAndSubmitCode() {
         if (!this.state.validateCode) {
             this.setState({formError: 'validateCodeForm.error.pleaseFillMagicCode'});
-        } else if (this.state.validateCode.length !== 6) {
+        } else if (!ValidationUtils.isValidValidateCode(this.state.validateCode)) {
             this.setState({formError: 'validateCodeForm.error.incorrectMagicCode'});
         } else {
             User.validateSecondaryLogin(this.getContactMethod(), this.state.validateCode);

From afe51f4143d74b07c9f7f8aa5ea15ee12d0341bf Mon Sep 17 00:00:00 2001
From: Alex Beaman <alexbeaman@expensify.com>
Date: Mon, 27 Mar 2023 17:00:33 +0000
Subject: [PATCH 22/28] Clear error on successful submit

---
 src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
index ff88d25c3aed..112ce6cbefb6 100644
--- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
+++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
@@ -135,6 +135,7 @@ class ContactMethodDetailsPage extends Component {
         } else if (!ValidationUtils.isValidValidateCode(this.state.validateCode)) {
             this.setState({formError: 'validateCodeForm.error.incorrectMagicCode'});
         } else {
+            this.setState({formError: ''});
             User.validateSecondaryLogin(this.getContactMethod(), this.state.validateCode);
         }
     }

From 5749c3bdfbadce43a7f893b734376d92f238a1f6 Mon Sep 17 00:00:00 2001
From: Aldo Canepa <aldo@expensify.com>
Date: Mon, 27 Mar 2023 17:01:25 +0000
Subject: [PATCH 23/28] Only handle touch in resend validate code text

---
 .../settings/Profile/Contacts/ContactMethodDetailsPage.js  | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
index 112ce6cbefb6..03cf26052f83 100644
--- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
+++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
@@ -194,17 +194,16 @@ class ContactMethodDetailsPage extends Component {
                                 errorRowStyles={[styles.mt2]}
                                 onClose={() => User.clearContactMethodErrors(contactMethod, 'validateCodeSent')}
                             >
-                                <TouchableOpacity
+                                <View
                                     style={[styles.mt2, styles.dFlex, styles.flexRow]}
-                                    onPress={this.resendValidateCode}
                                 >
-                                    <Text style={[styles.link, styles.mr4]}>
+                                    <Text style={[styles.link, styles.mr4]} onPress={this.resendValidateCode}>
                                         {this.props.translate('contacts.resendMagicCode')}
                                     </Text>
                                     {hasMagicCodeBeenSent && (
                                         <Icon src={Expensicons.Checkmark} fill={colors.green} />
                                     )}
-                                </TouchableOpacity>
+                                </View>
                             </OfflineWithFeedback>
                             <OfflineWithFeedback
                                 pendingAction={lodashGet(loginData, 'pendingFields.validateLogin', null)}

From eb71724b1b7e7b40fc42a2b2d3c5b0b72cdcc33b Mon Sep 17 00:00:00 2001
From: Aldo Canepa <aldo@expensify.com>
Date: Mon, 27 Mar 2023 17:42:26 +0000
Subject: [PATCH 24/28] 4px to the left

---
 src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
index 03cf26052f83..0034d413a145 100644
--- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
+++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
@@ -197,7 +197,7 @@ class ContactMethodDetailsPage extends Component {
                                 <View
                                     style={[styles.mt2, styles.dFlex, styles.flexRow]}
                                 >
-                                    <Text style={[styles.link, styles.mr4]} onPress={this.resendValidateCode}>
+                                    <Text style={[styles.link, styles.mr3]} onPress={this.resendValidateCode}>
                                         {this.props.translate('contacts.resendMagicCode')}
                                     </Text>
                                     {hasMagicCodeBeenSent && (

From f95dbdad234a284da70f42a9c64ccda6390095d1 Mon Sep 17 00:00:00 2001
From: Alex Beaman <alexbeaman@expensify.com>
Date: Mon, 27 Mar 2023 20:36:08 +0000
Subject: [PATCH 25/28] Fix lint issues

---
 .../settings/Profile/Contacts/ContactMethodDetailsPage.js     | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
index 0034d413a145..6087f2ef7358 100644
--- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
+++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
@@ -1,7 +1,7 @@
 import Str from 'expensify-common/lib/str';
 import lodashGet from 'lodash/get';
 import React, {Component} from 'react';
-import {View, ScrollView, TouchableOpacity} from 'react-native';
+import {View, ScrollView} from 'react-native';
 import PropTypes from 'prop-types';
 import {withOnyx} from 'react-native-onyx';
 import Navigation from '../../../../libs/Navigation/Navigation';
@@ -26,7 +26,7 @@ import Button from '../../../../components/Button';
 import * as ErrorUtils from '../../../../libs/ErrorUtils';
 import themeColors from '../../../../styles/themes/default';
 import NotFoundPage from '../../../ErrorPage/NotFoundPage';
-import * as ValidationUtils from '../../../../libs/ValidationUtils'
+import * as ValidationUtils from '../../../../libs/ValidationUtils';
 
 const propTypes = {
     /* Onyx Props */

From 6cc7921eb6c8f64882c624371b0cb7b446b122e0 Mon Sep 17 00:00:00 2001
From: Alex Beaman <alexbeaman@expensify.com>
Date: Mon, 27 Mar 2023 20:37:06 +0000
Subject: [PATCH 26/28] Make resend checkmark 4 px from text

---
 src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
index 6087f2ef7358..15edcd582589 100644
--- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
+++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
@@ -197,7 +197,7 @@ class ContactMethodDetailsPage extends Component {
                                 <View
                                     style={[styles.mt2, styles.dFlex, styles.flexRow]}
                                 >
-                                    <Text style={[styles.link, styles.mr3]} onPress={this.resendValidateCode}>
+                                    <Text style={[styles.link, styles.mr1]} onPress={this.resendValidateCode}>
                                         {this.props.translate('contacts.resendMagicCode')}
                                     </Text>
                                     {hasMagicCodeBeenSent && (

From 6151893d35ea4170028b96fd3ef536ba8a81d945 Mon Sep 17 00:00:00 2001
From: Aldo Canepa <aldo@expensify.com>
Date: Tue, 28 Mar 2023 15:54:39 +0000
Subject: [PATCH 27/28] Fix bug requiring double tap to submit

---
 src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
index 15edcd582589..f42ec805fd4e 100644
--- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
+++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
@@ -159,7 +159,7 @@ class ContactMethodDetailsPage extends Component {
                     onBackButtonPress={() => Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS)}
                     onCloseButtonPress={() => Navigation.dismissModal(true)}
                 />
-                <ScrollView>
+                <ScrollView keyboardShouldPersistTaps="handled">
                     <ConfirmModal
                         title={this.props.translate('contacts.removeContactMethod')}
                         onConfirm={this.confirmDeleteAndHideModal}

From 82dd1a4edefe16ea11c453aa2b981ea6c40925e7 Mon Sep 17 00:00:00 2001
From: Alex Beaman <alexbeaman@expensify.com>
Date: Tue, 28 Mar 2023 17:18:30 +0000
Subject: [PATCH 28/28] All keypad input to blur on submit

---
 src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
index f42ec805fd4e..eca350a4e691 100644
--- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
+++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
@@ -185,7 +185,6 @@ class ContactMethodDetailsPage extends Component {
                                 value={this.state.validateCode}
                                 onChangeText={text => this.setState({validateCode: text})}
                                 keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD}
-                                blurOnSubmit={false}
                                 errorText={formErrorText}
                             />
                             <OfflineWithFeedback
