diff --git a/src/ROUTES.js b/src/ROUTES.js index 016c636f5bd2..74f3ba233bdf 100644 --- a/src/ROUTES.js +++ b/src/ROUTES.js @@ -14,6 +14,7 @@ const IOU_REQUEST_CURRENCY = `${IOU_REQUEST}/currency`; const IOU_BILL_CURRENCY = `${IOU_BILL}/currency`; const IOU_SEND_CURRENCY = `${IOU_SEND}/currency`; const SETTINGS_PERSONAL_DETAILS = 'settings/profile/personal-details'; +const SETTINGS_CONTACT_METHODS = 'settings/profile/contact-methods'; export default { BANK_ACCOUNT: 'bank-account', @@ -49,6 +50,7 @@ export default { SETTINGS_PERSONAL_DETAILS_LEGAL_NAME: `${SETTINGS_PERSONAL_DETAILS}/legal-name`, SETTINGS_PERSONAL_DETAILS_DATE_OF_BIRTH: `${SETTINGS_PERSONAL_DETAILS}/date-of-birth`, SETTINGS_PERSONAL_DETAILS_ADDRESS: `${SETTINGS_PERSONAL_DETAILS}/address`, + SETTINGS_CONTACT_METHODS, NEW_GROUP: 'new/group', NEW_CHAT: 'new/chat', REPORT, diff --git a/src/languages/en.js b/src/languages/en.js index ad9310d9dcd1..1e254c64f53e 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -339,6 +339,10 @@ export default { pronouns: 'Pronouns', isShownOnProfile: 'Your pronouns are shown on your profile.', }, + contacts: { + contactMethod: 'Contact method', + contactMethods: 'Contact methods', + }, pronouns: { coCos: 'Co / Cos', eEyEmEir: 'E / Ey / Em / Eir', diff --git a/src/languages/es.js b/src/languages/es.js index e299f3a7db0f..0ecabf17c462 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -339,6 +339,10 @@ export default { pronouns: 'Pronombres', isShownOnProfile: 'Tus pronombres se muestran en tu perfil.', }, + contacts: { + contactMethod: 'Método de contacto', + contactMethods: 'Métodos de contacto', + }, pronouns: { coCos: 'Co / Cos', eEyEmEir: 'E / Ey / Em / Eir', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js index 607ee566c6cd..07c67bec110e 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js @@ -275,7 +275,14 @@ const SettingsModalStackNavigator = createModalStackNavigator([ }, { getComponent: () => { - const SettingsAddSecondaryLoginPage = require('../../../pages/settings/AddSecondaryLoginPage').default; + const SettingsContactMethodsPage = require('../../../pages/settings/Profile/Contacts/ContactMethodsPage').default; + return SettingsContactMethodsPage; + }, + name: 'Settings_ContactMethods', + }, + { + getComponent: () => { + const SettingsAddSecondaryLoginPage = require('../../../pages/settings/Profile/Contacts/AddSecondaryLoginPage').default; return SettingsAddSecondaryLoginPage; }, name: 'Settings_Add_Secondary_Login', diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js index 12b31245caf0..361655c31439 100644 --- a/src/libs/Navigation/linkingConfig.js +++ b/src/libs/Navigation/linkingConfig.js @@ -120,6 +120,10 @@ export default { path: ROUTES.SETTINGS_APP_DOWNLOAD_LINKS, exact: true, }, + Settings_ContactMethods: { + path: ROUTES.SETTINGS_CONTACT_METHODS, + exact: true, + }, Settings_Add_Secondary_Login: { path: ROUTES.SETTINGS_ADD_LOGIN, }, diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js index a74a7350e06a..e9f4bcf6cb24 100644 --- a/src/libs/actions/User.js +++ b/src/libs/actions/User.js @@ -141,7 +141,7 @@ function setSecondaryLoginAndNavigate(login, password) { }).then((response) => { if (response.jsonCode === 200) { Onyx.set(ONYXKEYS.LOGIN_LIST, response.loginList); - Navigation.navigate(ROUTES.SETTINGS_PROFILE); + Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS); return; } diff --git a/src/pages/settings/AddSecondaryLoginPage.js b/src/pages/settings/Profile/Contacts/AddSecondaryLoginPage.js similarity index 86% rename from src/pages/settings/AddSecondaryLoginPage.js rename to src/pages/settings/Profile/Contacts/AddSecondaryLoginPage.js index 982736ea1186..db375e6ba80e 100755 --- a/src/pages/settings/AddSecondaryLoginPage.js +++ b/src/pages/settings/Profile/Contacts/AddSecondaryLoginPage.js @@ -4,22 +4,22 @@ 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 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'; const propTypes = { /* Onyx Props */ @@ -104,7 +104,7 @@ class AddSecondaryLoginPage extends Component { ? 'addSecondaryLoginPage.addPhoneNumber' : 'addSecondaryLoginPage.addEmailAddress')} shouldShowBackButton - onBackButtonPress={() => Navigation.navigate(ROUTES.SETTINGS_PROFILE)} + onBackButtonPress={() => 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 */} diff --git a/src/pages/settings/Profile/Contacts/ContactMethodsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodsPage.js new file mode 100644 index 000000000000..edb998047c11 --- /dev/null +++ b/src/pages/settings/Profile/Contacts/ContactMethodsPage.js @@ -0,0 +1,138 @@ +import Str from 'expensify-common/lib/str'; +import lodashGet from 'lodash/get'; +import PropTypes from 'prop-types'; +import React, {Component} from 'react'; +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'; +import Navigation from '../../../../libs/Navigation/Navigation'; +import ONYXKEYS from '../../../../ONYXKEYS'; +import ROUTES from '../../../../ROUTES'; +import LoginField from './LoginField'; + +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 of when login was validated */ + validatedDate: PropTypes.string, + }), + + ...withLocalizePropTypes, + ...withCurrentUserPersonalDetailsPropTypes, +}; + +const defaultProps = { + loginList: {}, + ...withCurrentUserPersonalDetailsDefaultProps, +}; + +class ContactMethodsPage extends Component { + constructor(props) { + super(props); + + this.state = { + logins: this.getLogins(), + }; + + this.getLogins = this.getLogins.bind(this); + } + + componentDidUpdate(prevProps) { + let stateToUpdate = {}; + + // Recalculate logins if loginList has changed + if (_.keys(this.props.loginList).length !== _.keys(prevProps.loginList).length) { + stateToUpdate = {logins: this.getLogins()}; + } + + if (_.isEmpty(stateToUpdate)) { + return; + } + + // 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() { + return ( + + Navigation.navigate(ROUTES.SETTINGS_PROFILE)} + onCloseButtonPress={() => Navigation.dismissModal(true)} + /> + + + + + + ); + } +} + +ContactMethodsPage.propTypes = propTypes; +ContactMethodsPage.defaultProps = defaultProps; + +export default compose( + withLocalize, + withCurrentUserPersonalDetails, + withOnyx({ + loginList: { + key: ONYXKEYS.LOGIN_LIST, + }, + }), +)(ContactMethodsPage); diff --git a/src/pages/settings/Profile/LoginField.js b/src/pages/settings/Profile/Contacts/LoginField.js similarity index 87% rename from src/pages/settings/Profile/LoginField.js rename to src/pages/settings/Profile/Contacts/LoginField.js index 055000a045b9..1fff1838ba9c 100755 --- a/src/pages/settings/Profile/LoginField.js +++ b/src/pages/settings/Profile/Contacts/LoginField.js @@ -1,18 +1,18 @@ 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'; +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 */ diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js index 3af2527a7b3f..6672198dc7db 100755 --- a/src/pages/settings/Profile/ProfilePage.js +++ b/src/pages/settings/Profile/ProfilePage.js @@ -1,10 +1,8 @@ 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 AvatarWithImagePicker from '../../../components/AvatarWithImagePicker'; import HeaderWithCloseButton from '../../../components/HeaderWithCloseButton'; @@ -19,189 +17,102 @@ import * as PersonalDetails from '../../../libs/actions/PersonalDetails'; import compose from '../../../libs/compose'; import Navigation from '../../../libs/Navigation/Navigation'; import * as ReportUtils from '../../../libs/ReportUtils'; -import ONYXKEYS from '../../../ONYXKEYS'; import ROUTES from '../../../ROUTES'; import styles from '../../../styles/styles'; -import LoginField from './LoginField'; import * as Expensicons from '../../../components/Icon/Expensicons'; 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 of when login was validated */ - validatedDate: PropTypes.string, - }), - ...withLocalizePropTypes, ...withCurrentUserPersonalDetailsPropTypes, }; const defaultProps = { - loginList: {}, ...withCurrentUserPersonalDetailsDefaultProps, }; -class ProfilePage extends Component { - constructor(props) { - super(props); - - this.defaultAvatar = ReportUtils.getDefaultAvatar(this.props.currentUserPersonalDetails.login); - this.avatar = {uri: lodashGet(this.props.currentUserPersonalDetails, 'avatar') || this.defaultAvatar}; - this.state = { - logins: this.getLogins(), - }; - - this.getLogins = this.getLogins.bind(this); - } - - componentDidUpdate(prevProps) { - let stateToUpdate = {}; - - // Recalculate logins if loginList has changed - if (_.keys(this.props.loginList).length !== _.keys(prevProps.loginList).length) { - stateToUpdate = {...stateToUpdate, logins: this.getLogins()}; - } - - if (_.isEmpty(stateToUpdate)) { - return; - } - - // eslint-disable-next-line react/no-did-update-set-state - this.setState(stateToUpdate); - } - - getPronouns() { - let pronounsKey = lodashGet(this.props.currentUserPersonalDetails, 'pronouns', ''); +const ProfilePage = (props) => { + const getPronouns = () => { + let pronounsKey = lodashGet(props.currentUserPersonalDetails, 'pronouns', ''); if (pronounsKey.startsWith(CONST.PRONOUNS.PREFIX)) { pronounsKey = pronounsKey.slice(CONST.PRONOUNS.PREFIX.length); } - return lodashGet(this.props.translate('pronouns'), pronounsKey, this.props.translate('profilePage.selectYourPronouns')); - } - - /** - * 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() { - const currentUserDetails = this.props.currentUserPersonalDetails || {}; - const profileSettingsOptions = [ - { - description: this.props.translate('displayNamePage.headerTitle'), - title: lodashGet(currentUserDetails, 'displayName', ''), - pageRoute: ROUTES.SETTINGS_DISPLAY_NAME, - }, - { - description: this.props.translate('pronounsPage.pronouns'), - title: this.getPronouns(), - pageRoute: ROUTES.SETTINGS_PRONOUNS, - }, - { - description: this.props.translate('timezonePage.timezone'), - title: `${lodashGet(currentUserDetails, 'timezone.selected', '')}`, - pageRoute: ROUTES.SETTINGS_TIMEZONE, - }, - ]; - return ( - - Navigation.navigate(ROUTES.SETTINGS)} - onCloseButtonPress={() => Navigation.dismissModal(true)} - /> - - - - - - {_.map(profileSettingsOptions, (detail, index) => ( - Navigation.navigate(detail.pageRoute)} - /> - ))} - - - - Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS)} - shouldShowRightIcon + return lodashGet(props.translate('pronouns'), pronounsKey, props.translate('profilePage.selectYourPronouns')); + }; + const currentUserDetails = props.currentUserPersonalDetails || {}; + const profileSettingsOptions = [ + { + description: props.translate('displayNamePage.headerTitle'), + title: lodashGet(currentUserDetails, 'displayName', ''), + pageRoute: ROUTES.SETTINGS_DISPLAY_NAME, + }, + { + description: props.translate('contacts.contactMethod'), + title: Str.removeSMSDomain(lodashGet(currentUserDetails, 'login', '')), + pageRoute: ROUTES.SETTINGS_CONTACT_METHODS, + }, + { + description: props.translate('pronounsPage.pronouns'), + title: getPronouns(), + pageRoute: ROUTES.SETTINGS_PRONOUNS, + }, + { + description: props.translate('timezonePage.timezone'), + title: `${lodashGet(currentUserDetails, 'timezone.selected', '')}`, + pageRoute: ROUTES.SETTINGS_TIMEZONE, + }, + ]; + + return ( + + Navigation.navigate(ROUTES.SETTINGS)} + onCloseButtonPress={() => Navigation.dismissModal(true)} + /> + + + - - - ); - } -} + + + {_.map(profileSettingsOptions, (detail, index) => ( + Navigation.navigate(detail.pageRoute)} + /> + ))} + + Navigation.navigate(ROUTES.SETTINGS_PERSONAL_DETAILS)} + shouldShowRightIcon + /> + + + ); +}; ProfilePage.propTypes = propTypes; ProfilePage.defaultProps = defaultProps; +ProfilePage.displayName = 'ProfilePage'; export default compose( withLocalize, withCurrentUserPersonalDetails, - withOnyx({ - loginList: { - key: ONYXKEYS.LOGIN_LIST, - }, - }), )(ProfilePage);