diff --git a/src/ROUTES.js b/src/ROUTES.js
index 2412e7e42550..4dc2f96193f7 100644
--- a/src/ROUTES.js
+++ b/src/ROUTES.js
@@ -42,7 +42,6 @@ export default {
SETTINGS_ADD_DEBIT_CARD: 'settings/payments/add-debit-card',
SETTINGS_ADD_BANK_ACCOUNT: 'settings/payments/add-bank-account',
SETTINGS_ENABLE_PAYMENTS: 'settings/payments/enable-payments',
- SETTINGS_ADD_LOGIN: 'settings/addlogin/:type',
getSettingsAddLoginRoute: type => `settings/addlogin/${type}`,
SETTINGS_PAYMENTS_TRANSFER_BALANCE: 'settings/payments/transfer-balance',
SETTINGS_PAYMENTS_CHOOSE_TRANSFER_ACCOUNT: 'settings/payments/choose-transfer-account',
@@ -53,6 +52,7 @@ export default {
SETTINGS_CONTACT_METHODS,
SETTINGS_CONTACT_METHOD_DETAILS: `${SETTINGS_CONTACT_METHODS}/:contactMethod/details`,
getEditContactMethodRoute: contactMethod => `${SETTINGS_CONTACT_METHODS}/${encodeURIComponent(contactMethod)}/details`,
+ SETTINGS_NEW_CONTACT_METHOD: `${SETTINGS_CONTACT_METHODS}/new`,
NEW_GROUP: 'new/group',
NEW_CHAT: 'new/chat',
REPORT,
diff --git a/src/languages/en.js b/src/languages/en.js
index cb7487a19f70..53f5876d37f7 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -366,11 +366,14 @@ export 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',
+ failedNewContact: 'Failed to add this contact method.',
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.',
+ addContactMethod: 'Failed to add this contact method. Please reach out to Concierge for help.',
},
+ newContactMethod: 'New contact method',
},
pronouns: {
coCos: 'Co / Cos',
@@ -404,13 +407,6 @@ export default {
isShownOnProfile: 'Your timezone is shown on your profile.',
getLocationAutomatically: 'Automatically determine your location.',
},
- addSecondaryLoginPage: {
- addPhoneNumber: 'Add phone number',
- addEmailAddress: 'Add email address',
- enterPreferredPhoneNumberToSendValidationLink: 'Enter your preferred phone number to send a validation link.',
- enterPreferredEmailToSendValidationLink: 'Enter your preferred email address to send a validation link.',
- sendValidation: 'Send validation',
- },
initialSettingsPage: {
about: 'About',
aboutPage: {
diff --git a/src/languages/es.js b/src/languages/es.js
index 319cd15c58e0..ab70f0ff4558 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -365,11 +365,14 @@ export default {
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',
+ failedNewContact: 'Hubo un error al añadir este método de contacto.',
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.',
+ addContactMethod: 'Hubo un error al añadir este método de contacto. Por favor contacta con Concierge para obtener ayuda.',
},
+ newContactMethod: 'Nuevo método de contacto',
},
pronouns: {
coCos: 'Co / Cos',
@@ -403,13 +406,6 @@ export default {
isShownOnProfile: 'Tu zona horaria se muestra en tu perfil.',
getLocationAutomatically: 'Detecta tu ubicación automáticamente.',
},
- addSecondaryLoginPage: {
- addPhoneNumber: 'Agregar número de teléfono',
- addEmailAddress: 'Agregar dirección de email',
- enterPreferredPhoneNumberToSendValidationLink: 'Escribe tu número de teléfono para recibir el enlace de validación.',
- enterPreferredEmailToSendValidationLink: 'Escribe tu email para recibir el enlace de validación.',
- sendValidation: 'Enviar validación',
- },
initialSettingsPage: {
about: 'Acerca de',
aboutPage: {
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
index 7b46354e9391..3fa206018cf8 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js
@@ -289,10 +289,10 @@ const SettingsModalStackNavigator = createModalStackNavigator([
},
{
getComponent: () => {
- const SettingsAddSecondaryLoginPage = require('../../../pages/settings/Profile/Contacts/AddSecondaryLoginPage').default;
- return SettingsAddSecondaryLoginPage;
+ const SettingsNewContactMethodPage = require('../../../pages/settings/Profile/Contacts/NewContactMethodPage').default;
+ return SettingsNewContactMethodPage;
},
- name: 'Settings_Add_Secondary_Login',
+ name: 'Settings_NewContactMethod',
},
{
getComponent: () => {
diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js
index a096a7421867..76e00c675852 100644
--- a/src/libs/Navigation/linkingConfig.js
+++ b/src/libs/Navigation/linkingConfig.js
@@ -127,8 +127,9 @@ export default {
Settings_ContactMethodDetails: {
path: ROUTES.SETTINGS_CONTACT_METHOD_DETAILS,
},
- Settings_Add_Secondary_Login: {
- path: ROUTES.SETTINGS_ADD_LOGIN,
+ Settings_NewContactMethod: {
+ path: ROUTES.SETTINGS_NEW_CONTACT_METHOD,
+ exact: true,
},
Settings_PersonalDetails_Initial: {
path: ROUTES.SETTINGS_PERSONAL_DETAILS,
diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js
index 1c9a296c975e..b016ec2110ef 100644
--- a/src/libs/actions/User.js
+++ b/src/libs/actions/User.js
@@ -3,7 +3,6 @@ import lodashGet from 'lodash/get';
import Onyx from 'react-native-onyx';
import moment from 'moment';
import ONYXKEYS from '../../ONYXKEYS';
-import * as DeprecatedAPI from '../deprecatedAPI';
import * as API from '../API';
import CONFIG from '../../CONFIG';
import CONST from '../../CONST';
@@ -182,42 +181,6 @@ function updateNewsletterSubscription(isSubscribed) {
});
}
-/**
- * Adds a secondary login to a user's account
- *
- * @param {String} login
- * @param {String} password
- * @returns {Promise}
- */
-function setSecondaryLoginAndNavigate(login, password) {
- Onyx.merge(ONYXKEYS.ACCOUNT, {...CONST.DEFAULT_ACCOUNT_DATA, isLoading: true});
-
- return DeprecatedAPI.User_SecondaryLogin_Send({
- email: login,
- password,
- }).then((response) => {
- if (response.jsonCode === 200) {
- Onyx.set(ONYXKEYS.LOGIN_LIST, response.loginList);
- Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS);
- return;
- }
-
- let error = lodashGet(response, 'message', 'Unable to add secondary login. Please try again.');
-
- // Replace error with a friendlier message
- if (error.includes('already belongs to an existing Expensify account.')) {
- error = 'This login already belongs to an existing Expensify account.';
- }
- if (error.includes('I couldn\'t validate the phone number')) {
- error = Localize.translateLocal('common.error.phoneNumber');
- }
-
- Onyx.merge(ONYXKEYS.USER, {error});
- }).finally(() => {
- Onyx.merge(ONYXKEYS.ACCOUNT, {isLoading: false});
- });
-}
-
/**
* Delete a specific contact method
*
@@ -225,6 +188,12 @@ function setSecondaryLoginAndNavigate(login, password) {
* @param {Object} oldLoginData
*/
function deleteContactMethod(contactMethod, oldLoginData) {
+ // If the contact method failed to be added to the account, then it should only be deleted locally.
+ if (lodashGet(oldLoginData, 'errorFields.addedLogin', null)) {
+ Onyx.merge(ONYXKEYS.LOGIN_LIST, {[contactMethod]: null});
+ return;
+ }
+
const optimisticData = [{
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.LOGIN_LIST,
@@ -290,6 +259,61 @@ function clearContactMethodErrors(contactMethod, fieldName) {
});
}
+/**
+ * Adds a secondary login to a user's account
+ *
+ * @param {String} contactMethod
+ * @param {String} password
+ */
+function addNewContactMethodAndNavigate(contactMethod, password) {
+ const optimisticData = [{
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: ONYXKEYS.LOGIN_LIST,
+ value: {
+ [contactMethod]: {
+ partnerUserID: contactMethod,
+ validatedDate: '',
+ errorFields: {
+ addedLogin: null,
+ },
+ pendingFields: {
+ addedLogin: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
+ },
+ },
+ },
+ }];
+ const successData = [{
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: ONYXKEYS.LOGIN_LIST,
+ value: {
+ [contactMethod]: {
+ pendingFields: {
+ addedLogin: null,
+ },
+ },
+ },
+ }];
+ const failureData = [{
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: ONYXKEYS.LOGIN_LIST,
+ value: {
+ [contactMethod]: {
+ errorFields: {
+ addedLogin: {
+ [DateUtils.getMicroseconds()]: Localize.translateLocal('contacts.genericFailureMessages.addContactMethod'),
+ },
+ },
+ pendingFields: {
+ addedLogin: null,
+ },
+ },
+ },
+ }];
+
+ API.write('AddNewContactMethod', {partnerUserID: contactMethod, password}, {optimisticData, successData, failureData});
+ Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS);
+}
+
/**
* Validates a login given an accountID and validation code
*
@@ -660,9 +684,9 @@ export {
resendValidateCode,
requestContactMethodValidateCode,
updateNewsletterSubscription,
- setSecondaryLoginAndNavigate,
deleteContactMethod,
clearContactMethodErrors,
+ addNewContactMethodAndNavigate,
validateLogin,
validateSecondaryLogin,
isBlockedFromConcierge,
diff --git a/src/pages/settings/Profile/Contacts/AddSecondaryLoginPage.js b/src/pages/settings/Profile/Contacts/AddSecondaryLoginPage.js
deleted file mode 100755
index 48232251df5b..000000000000
--- a/src/pages/settings/Profile/Contacts/AddSecondaryLoginPage.js
+++ /dev/null
@@ -1,187 +0,0 @@
-import React, {Component} from 'react';
-import {withOnyx} from 'react-native-onyx';
-import PropTypes from 'prop-types';
-import {View, ScrollView} from 'react-native';
-import _ from 'underscore';
-import Str from 'expensify-common/lib/str';
-import HeaderWithCloseButton from '../../../../components/HeaderWithCloseButton';
-import Navigation from '../../../../libs/Navigation/Navigation';
-import ScreenWrapper from '../../../../components/ScreenWrapper';
-import Text from '../../../../components/Text';
-import styles from '../../../../styles/styles';
-import * as User from '../../../../libs/actions/User';
-import ONYXKEYS from '../../../../ONYXKEYS';
-import Button from '../../../../components/Button';
-import ROUTES from '../../../../ROUTES';
-import CONST from '../../../../CONST';
-import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize';
-import compose from '../../../../libs/compose';
-import FixedFooter from '../../../../components/FixedFooter';
-import TextInput from '../../../../components/TextInput';
-import userPropTypes from '../../userPropTypes';
-import * as LoginUtils from '../../../../libs/LoginUtils';
-import Permissions from '../../../../libs/Permissions';
-
-const propTypes = {
- /* Onyx Props */
- user: userPropTypes,
-
- // Route object from navigation
- route: PropTypes.shape({
- // Params that are passed into the route
- params: PropTypes.shape({
- // The type of secondary login to be added (email|phone)
- type: PropTypes.string,
- }),
- }),
-
- /** List of betas available to current user */
- betas: PropTypes.arrayOf(PropTypes.string),
-
- ...withLocalizePropTypes,
-};
-
-const defaultProps = {
- user: {},
- route: {},
- betas: [],
-};
-
-class AddSecondaryLoginPage extends Component {
- constructor(props) {
- super(props);
-
- this.state = {
- login: '',
- password: '',
- };
- this.formType = props.route.params.type;
- this.submitForm = this.submitForm.bind(this);
- this.onSecondaryLoginChange = this.onSecondaryLoginChange.bind(this);
- this.validateForm = this.validateForm.bind(this);
- }
-
- componentWillUnmount() {
- User.clearUserErrorMessage();
- }
-
- onSecondaryLoginChange(login) {
- this.setState({login});
- }
-
- /**
- * Add a secondary login to a user's account
- */
- submitForm() {
- const login = this.formType === CONST.LOGIN_TYPE.PHONE
- ? LoginUtils.getPhoneNumberWithoutSpecialChars(this.state.login)
- : this.state.login;
- User.setSecondaryLoginAndNavigate(login, this.state.password);
- }
-
- /**
- * Determine whether the form is valid
- *
- * @returns {Boolean}
- */
- validateForm() {
- const login = this.formType === CONST.LOGIN_TYPE.PHONE
- ? LoginUtils.getPhoneNumberWithoutSpecialChars(this.state.login)
- : this.state.login;
-
- const validationMethod = this.formType === CONST.LOGIN_TYPE.PHONE ? Str.isValidPhone : Str.isValidEmail;
- if (!validationMethod(login)) {
- return false;
- }
-
- return !Permissions.canUsePasswordlessLogins(this.props.betas) && !this.state.password;
- }
-
- render() {
- return (
- {
- if (!this.phoneNumberInputRef) {
- return;
- }
-
- this.phoneNumberInputRef.focus();
- }}
- >
- Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS)}
- onCloseButtonPress={() => Navigation.dismissModal()}
- />
- {/* We use keyboardShouldPersistTaps="handled" to prevent the keyboard from being hidden when switching focus on input fields */}
-
-
- {this.props.translate(this.formType === CONST.LOGIN_TYPE.PHONE
- ? 'addSecondaryLoginPage.enterPreferredPhoneNumberToSendValidationLink'
- : 'addSecondaryLoginPage.enterPreferredEmailToSendValidationLink')}
-
-
- this.phoneNumberInputRef = el}
- value={this.state.login}
- onChangeText={this.onSecondaryLoginChange}
- keyboardType={this.formType === CONST.LOGIN_TYPE.PHONE
- ? CONST.KEYBOARD_TYPE.PHONE_PAD : CONST.KEYBOARD_TYPE.EMAIL_ADDRESS}
- returnKeyType="done"
- autoComplete={this.formType === CONST.LOGIN_TYPE.PHONE ? 'tel' : 'email'}
- />
-
- {!Permissions.canUsePasswordlessLogins(this.props.betas) && (
-
- this.setState({password})}
- secureTextEntry
- autoCompleteType="password"
- textContentType="password"
- onSubmitEditing={this.submitForm}
- />
-
- )}
- {!_.isEmpty(this.props.user.error) && (
-
- {this.props.user.error}
-
- )}
-
-
-
-
-
- );
- }
-}
-
-AddSecondaryLoginPage.propTypes = propTypes;
-AddSecondaryLoginPage.defaultProps = defaultProps;
-
-export default compose(
- withLocalize,
- withOnyx({
- user: {
- key: ONYXKEYS.USER,
- },
- betas: {
- key: ONYXKEYS.BETAS,
- },
- }),
-)(AddSecondaryLoginPage);
diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
index eca350a4e691..ba5c0e5fbb5b 100644
--- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
+++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
@@ -1,5 +1,6 @@
import Str from 'expensify-common/lib/str';
import lodashGet from 'lodash/get';
+import _ from 'underscore';
import React, {Component} from 'react';
import {View, ScrollView} from 'react-native';
import PropTypes from 'prop-types';
@@ -16,6 +17,7 @@ import styles from '../../../../styles/styles';
import * as Expensicons from '../../../../components/Icon/Expensicons';
import Text from '../../../../components/Text';
import OfflineWithFeedback from '../../../../components/OfflineWithFeedback';
+import DotIndicatorMessage from '../../../../components/DotIndicatorMessage';
import ConfirmModal from '../../../../components/ConfirmModal';
import * as User from '../../../../libs/actions/User';
import TextInput from '../../../../components/TextInput';
@@ -81,6 +83,7 @@ class ContactMethodDetailsPage extends Component {
constructor(props) {
super(props);
+ this.deleteContactMethod = this.deleteContactMethod.bind(this);
this.toggleDeleteModal = this.toggleDeleteModal.bind(this);
this.confirmDeleteAndHideModal = this.confirmDeleteAndHideModal.bind(this);
this.resendValidateCode = this.resendValidateCode.bind(this);
@@ -103,6 +106,17 @@ class ContactMethodDetailsPage extends Component {
return decodeURIComponent(lodashGet(this.props.route, 'params.contactMethod'));
}
+ /**
+ * Deletes the contact method if it has errors. Otherwise, it shows the confirmation alert and deletes it only if the user confirms.
+ */
+ deleteContactMethod() {
+ if (!_.isEmpty(lodashGet(this.props.loginList, [this.getContactMethod(), 'errorFields'], {}))) {
+ User.deleteContactMethod(this.getContactMethod());
+ return;
+ }
+ this.toggleDeleteModal(true);
+ }
+
/**
* Toggle delete confirm modal visibility
* @param {Boolean} isOpen
@@ -150,6 +164,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) : '';
+ const isFailedAddContactMethod = Boolean(lodashGet(loginData, 'errorFields.addedLogin'));
return (
@@ -169,7 +184,8 @@ class ContactMethodDetailsPage extends Component {
isVisible={this.state.isDeleteModalOpen}
danger
/>
- {!loginData.validatedDate && (
+ {isFailedAddContactMethod && }
+ {!loginData.validatedDate && !isFailedAddContactMethod && (
@@ -235,7 +251,7 @@ class ContactMethodDetailsPage extends Component {
title={this.props.translate('common.remove')}
icon={Expensicons.Trashcan}
iconFill={themeColors.danger}
- onPress={() => this.toggleDeleteModal(true)}
+ onPress={this.deleteContactMethod}
/>
)}
diff --git a/src/pages/settings/Profile/Contacts/ContactMethodsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodsPage.js
index a205eb7da709..7d8e362a438e 100644
--- a/src/pages/settings/Profile/Contacts/ContactMethodsPage.js
+++ b/src/pages/settings/Profile/Contacts/ContactMethodsPage.js
@@ -6,6 +6,7 @@ import {View} from 'react-native';
import {ScrollView} from 'react-native-gesture-handler';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
+import Button from '../../../../components/Button';
import HeaderWithCloseButton from '../../../../components/HeaderWithCloseButton';
import ScreenWrapper from '../../../../components/ScreenWrapper';
import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize';
@@ -14,12 +15,12 @@ import compose from '../../../../libs/compose';
import Navigation from '../../../../libs/Navigation/Navigation';
import ONYXKEYS from '../../../../ONYXKEYS';
import ROUTES from '../../../../ROUTES';
-import LoginField from './LoginField';
+import styles from '../../../../styles/styles';
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';
+import FixedFooter from '../../../../components/FixedFooter';
const propTypes = {
/* Onyx Props */
@@ -58,11 +59,8 @@ const defaultProps = {
};
const ContactMethodsPage = (props) => {
- let hasPhoneNumberLogin = false;
- let hasEmailLogin = false;
-
const loginMenuItems = _.map(props.loginList, (login, loginName) => {
- const pendingAction = lodashGet(login, 'pendingFields.deletedLogin', null);
+ const pendingAction = lodashGet(login, 'pendingFields.deletedLogin') || lodashGet(login, 'pendingFields.addedLogin');
if (!login.partnerUserID && _.isEmpty(pendingAction)) {
return null;
}
@@ -70,6 +68,8 @@ const ContactMethodsPage = (props) => {
let description = '';
if (props.session.email === login.partnerUserID) {
description = props.translate('contacts.getInTouch');
+ } else if (lodashGet(login, 'errorFields.addedLogin')) {
+ description = props.translate('contacts.failedNewContact');
} else if (!login.validatedDate) {
description = props.translate('contacts.pleaseVerify');
}
@@ -80,19 +80,6 @@ const ContactMethodsPage = (props) => {
indicator = CONST.BRICK_ROAD_INDICATOR_STATUS.INFO;
}
- // Temporary checks to determine if we need to show specific LoginField
- // 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
- // that login type in the list here.
- if (Str.isValidPhone(Str.removeSMSDomain(login.partnerUserID))) {
- hasPhoneNumberLogin = true;
- } else if (Str.isValidEmail(login.partnerUserID)) {
- hasEmailLogin = true;
- }
-
// 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;
@@ -134,22 +121,15 @@ const ContactMethodsPage = (props) => {
{loginMenuItems}
- {/* The below fields will be removed soon, when we implement the new Add Contact Method page */}
- {!hasEmailLogin && (
-
- )}
- {!hasPhoneNumberLogin && (
-
- )}
+
+
);
};
diff --git a/src/pages/settings/Profile/Contacts/LoginField.js b/src/pages/settings/Profile/Contacts/LoginField.js
deleted file mode 100755
index 1fff1838ba9c..000000000000
--- a/src/pages/settings/Profile/Contacts/LoginField.js
+++ /dev/null
@@ -1,140 +0,0 @@
-import React, {Component} from 'react';
-import {View} from 'react-native';
-import PropTypes from 'prop-types';
-import Text from '../../../../components/Text';
-import styles from '../../../../styles/styles';
-import themeColors from '../../../../styles/themes/default';
-import * as Expensicons from '../../../../components/Icon/Expensicons';
-import Icon from '../../../../components/Icon';
-import ROUTES from '../../../../ROUTES';
-import CONST from '../../../../CONST';
-import Navigation from '../../../../libs/Navigation/Navigation';
-import * as User from '../../../../libs/actions/User';
-import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize';
-import Button from '../../../../components/Button';
-import MenuItemWithTopDescription from '../../../../components/MenuItemWithTopDescription';
-
-const propTypes = {
- /** Label to display on login form */
- label: PropTypes.string.isRequired,
-
- /** Type associated with the login */
- type: PropTypes.oneOf([CONST.LOGIN_TYPE.EMAIL, CONST.LOGIN_TYPE.PHONE]).isRequired,
-
- /** Login associated with the user */
- login: PropTypes.shape({
- /** Phone/Email associated with user */
- partnerUserID: PropTypes.string,
-
- /** Date of when login was validated */
- validatedDate: PropTypes.string,
- }).isRequired,
-
- ...withLocalizePropTypes,
-};
-
-class LoginField extends Component {
- constructor(props) {
- super(props);
- this.state = {
- showCheckmarkIcon: false,
- };
- this.timeout = null;
- this.onResendClicked = this.onResendClicked.bind(this);
- this.getTitle = this.getTitle.bind(this);
- }
-
- /**
- * Resend validation code and show the checkmark icon
- */
- onResendClicked() {
- User.resendValidateCode(this.props.login.partnerUserID);
- this.setState({showCheckmarkIcon: true});
-
- // Revert checkmark back to "Resend" after 5 seconds
- if (!this.timeout) {
- this.timeout = setTimeout(() => {
- if (!this.timeout) {
- return;
- }
-
- this.setState({showCheckmarkIcon: false});
- this.timeout = null;
- }, 5000);
- }
- }
-
- getTitle() {
- if (!this.props.login.partnerUserID) {
- return this.props.label;
- }
- if (this.props.type === CONST.LOGIN_TYPE.PHONE) {
- return this.props.toLocalPhone(this.props.login.partnerUserID);
- }
- return this.props.login.partnerUserID;
- }
-
- render() {
- let note;
- if (this.props.login.partnerUserID && !this.props.login.validatedDate) {
- if (this.props.type === CONST.LOGIN_TYPE.PHONE) {
- // Has unvalidated phone number
- note = this.props.translate('loginField.numberHasNotBeenValidated');
- } else {
- // Has unvalidated email
- note = this.props.translate('loginField.emailHasNotBeenValidated');
- }
- }
-
- return (
-
-
- {!this.props.login.partnerUserID || this.props.login.validatedDate ? (
-
- { } : () => Navigation.navigate(ROUTES.getSettingsAddLoginRoute(this.props.type))}
- shouldShowRightIcon={Boolean(!this.props.login.partnerUserID)}
- style={!this.props.login.partnerUserID ? styles.colorMuted : []}
- />
-
- ) : (
-
- {this.props.label}
-
-
- {this.props.type === CONST.LOGIN_TYPE.PHONE
- ? this.props.toLocalPhone(this.props.login.partnerUserID)
- : this.props.login.partnerUserID}
-
-
-
- )}
-
- {note && (
-
- {note}
-
- )}
-
- );
- }
-}
-
-LoginField.propTypes = propTypes;
-
-export default withLocalize(LoginField);
diff --git a/src/pages/settings/Profile/Contacts/NewContactMethodPage.js b/src/pages/settings/Profile/Contacts/NewContactMethodPage.js
new file mode 100644
index 000000000000..70e0860feb95
--- /dev/null
+++ b/src/pages/settings/Profile/Contacts/NewContactMethodPage.js
@@ -0,0 +1,159 @@
+import React, {Component} from 'react';
+import PropTypes from 'prop-types';
+import {View} from 'react-native';
+import {ScrollView} from 'react-native-gesture-handler';
+import {withOnyx} from 'react-native-onyx';
+import {compose} from 'underscore';
+import lodashGet from 'lodash/get';
+import Str from 'expensify-common/lib/str';
+import Button from '../../../../components/Button';
+import FixedFooter from '../../../../components/FixedFooter';
+import HeaderWithCloseButton from '../../../../components/HeaderWithCloseButton';
+import ScreenWrapper from '../../../../components/ScreenWrapper';
+import Text from '../../../../components/Text';
+import TextInput from '../../../../components/TextInput';
+import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize';
+import Navigation from '../../../../libs/Navigation/Navigation';
+import Permissions from '../../../../libs/Permissions';
+import ONYXKEYS from '../../../../ONYXKEYS';
+import ROUTES from '../../../../ROUTES';
+import styles from '../../../../styles/styles';
+import * as User from '../../../../libs/actions/User';
+import * as LoginUtils from '../../../../libs/LoginUtils';
+
+const propTypes = {
+ /* Onyx Props */
+
+ /** List of betas available to current user */
+ betas: PropTypes.arrayOf(PropTypes.string),
+
+ /** Login list for the user that is signed in */
+ loginList: PropTypes.shape({
+ /** The partner creating the account. It depends on the source: website, mobile, integrations, ... */
+ partnerName: PropTypes.string,
+
+ /** Phone/Email associated with user */
+ partnerUserID: PropTypes.string,
+
+ /** The date when the login was validated, used to show the brickroad 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)),
+ }),
+
+ ...withLocalizePropTypes,
+};
+const defaultProps = {
+ betas: [],
+ loginList: {},
+};
+
+class NewContactMethodPage extends Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ login: '',
+ password: '',
+ };
+ this.onLoginChange = this.onLoginChange.bind(this);
+ this.validateForm = this.validateForm.bind(this);
+ this.submitForm = this.submitForm.bind(this);
+ }
+
+ onLoginChange(login) {
+ this.setState({login});
+ }
+
+ /**
+ * Determine whether the form is valid
+ *
+ * @returns {Boolean}
+ */
+ validateForm() {
+ const login = this.state.login.trim();
+ const phoneLogin = LoginUtils.getPhoneNumberWithoutSpecialChars(login);
+
+ return (Permissions.canUsePasswordlessLogins(this.props.betas) || this.state.password)
+ && (Str.isValidEmail(login) || Str.isValidPhone(phoneLogin));
+ }
+
+ submitForm() {
+ // If this login already exists, just go back.
+ if (lodashGet(this.props.loginList, this.state.login)) {
+ Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS);
+ return;
+ }
+ User.addNewContactMethodAndNavigate(this.state.login, this.state.password);
+ }
+
+ render() {
+ return (
+ {
+ if (!this.loginInputRef) {
+ return;
+ }
+ this.loginInputRef.focus();
+ }}
+ >
+ Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS)}
+ onCloseButtonPress={() => Navigation.dismissModal(true)}
+ />
+
+
+ {this.props.translate('common.pleaseEnterEmailOrPhoneNumber')}
+
+
+ this.loginInputRef = el}
+ value={this.state.login}
+ onChangeText={this.onLoginChange}
+ autoCapitalize="none"
+ returnKeyType={Permissions.canUsePasswordlessLogins(this.props.betas) ? 'done' : 'next'}
+ />
+
+ {!Permissions.canUsePasswordlessLogins(this.props.betas)
+ && (
+
+ this.setState({password})}
+ returnKeyType="done"
+ />
+
+ )}
+
+
+
+
+
+ );
+ }
+}
+
+NewContactMethodPage.propTypes = propTypes;
+NewContactMethodPage.defaultProps = defaultProps;
+
+export default compose(
+ withLocalize,
+ withOnyx({
+ betas: {key: ONYXKEYS.BETAS},
+ loginList: {key: ONYXKEYS.LOGIN_LIST},
+ }),
+)(NewContactMethodPage);