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