diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js index e9a044f5c280..df7e70f6c6e0 100644 --- a/src/libs/actions/App.js +++ b/src/libs/actions/App.js @@ -1,3 +1,4 @@ +import moment from 'moment-timezone'; import {AppState, Linking} from 'react-native'; import Onyx from 'react-native-onyx'; import lodashGet from 'lodash/get'; @@ -19,10 +20,12 @@ import ROUTES from '../../ROUTES'; import * as SessionUtils from '../SessionUtils'; let currentUserAccountID; +let currentUserEmail = ''; Onyx.connect({ key: ONYXKEYS.SESSION, callback: (val) => { currentUserAccountID = lodashGet(val, 'accountID', ''); + currentUserEmail = lodashGet(val, 'email', ''); }, }); @@ -33,6 +36,12 @@ Onyx.connect({ initWithStoredValues: false, }); +let myPersonalDetails; +Onyx.connect({ + key: ONYXKEYS.PERSONAL_DETAILS, + callback: val => myPersonalDetails = val[currentUserEmail], +}); + const allPolicies = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.POLICY, @@ -206,6 +215,39 @@ function setUpPoliciesAndNavigate(session) { }); } +function openProfile() { + const oldTimezoneData = myPersonalDetails.timezone || {}; + const newTimezoneData = { + automatic: lodashGet(oldTimezoneData, 'automatic', true), + selected: moment.tz.guess(true), + }; + + API.write('OpenProfile', { + timezone: JSON.stringify(newTimezoneData), + }, { + optimisticData: [{ + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: ONYXKEYS.PERSONAL_DETAILS, + value: { + [currentUserEmail]: { + timezone: newTimezoneData, + }, + }, + }], + failureData: [{ + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: ONYXKEYS.PERSONAL_DETAILS, + value: { + [currentUserEmail]: { + timezone: oldTimezoneData, + }, + }, + }], + }); + + Navigation.navigate(ROUTES.SETTINGS_PROFILE); +} + // When the app reconnects from being offline, fetch all initialization data NetworkConnection.onReconnect(() => { getAppData(); @@ -219,5 +261,6 @@ export { getAppData, fixAccountAndReloadData, setUpPoliciesAndNavigate, + openProfile, openApp, }; diff --git a/src/libs/actions/PersonalDetails.js b/src/libs/actions/PersonalDetails.js index bcb939a1b2f1..4f40b1724429 100644 --- a/src/libs/actions/PersonalDetails.js +++ b/src/libs/actions/PersonalDetails.js @@ -262,6 +262,48 @@ function setPersonalDetails(details, shouldGrowl) { }); } +function updateProfile(firstName, lastName, pronouns, timezone) { + const myPersonalDetails = personalDetails[currentUserEmail]; + API.write('UpdateProfile', { + // 'details' is an old param that will be removed in https://github.com/Expensify/Expensify/issues/220321 + details: JSON.stringify({firstName, lastName, pronouns}), + firstName, + lastName, + pronouns, + timezone: JSON.stringify(timezone), + }, { + optimisticData: [{ + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: ONYXKEYS.PERSONAL_DETAILS, + value: { + [currentUserEmail]: { + firstName, + lastName, + pronouns, + timezone, + displayName: getDisplayName(currentUserEmail, { + firstName, + lastName, + }), + }, + }, + }], + failureData: [{ + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: ONYXKEYS.PERSONAL_DETAILS, + value: { + [currentUserEmail]: { + firstName: myPersonalDetails.firstName, + lastName: myPersonalDetails.lastName, + pronouns: myPersonalDetails.pronouns, + timezone: myPersonalDetails.timeZone, + displayName: myPersonalDetails.displayName, + }, + }, + }], + }); +} + /** * Fetches the local currency based on location and sets currency code/symbol to Onyx */ @@ -295,14 +337,30 @@ function setAvatar(file) { /** * Replaces the user's avatar image with a default avatar - * - * @param {String} defaultAvatarURL */ -function deleteAvatar(defaultAvatarURL) { - // We don't want to save the default avatar URL in the backend since we don't want to allow - // users the option of removing the default avatar, instead we'll save an empty string - DeprecatedAPI.PersonalDetails_Update({details: JSON.stringify({avatar: ''})}); - mergeLocalPersonalDetails({avatar: defaultAvatarURL}); +function deleteAvatar() { + const defaultAvatar = ReportUtils.getDefaultAvatar(currentUserEmail); + + API.write('DeleteUserAvatar', {}, { + optimisticData: [{ + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: ONYXKEYS.PERSONAL_DETAILS, + value: { + [currentUserEmail]: { + avatar: defaultAvatar, + }, + }, + }], + failureData: [{ + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: ONYXKEYS.PERSONAL_DETAILS, + value: { + [currentUserEmail]: { + avatar: personalDetails[currentUserEmail].avatar, + }, + }, + }], + }); } export { @@ -315,4 +373,5 @@ export { openIOUModalPage, getMaxCharacterError, extractFirstAndLastNameFromAvailableDetails, + updateProfile, }; diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js index cd1762ee6748..7686c5c3b399 100755 --- a/src/pages/settings/InitialSettingsPage.js +++ b/src/pages/settings/InitialSettingsPage.js @@ -20,10 +20,10 @@ import ROUTES from '../../ROUTES'; import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; import compose from '../../libs/compose'; import CONST from '../../CONST'; -import DateUtils from '../../libs/DateUtils'; import Permissions from '../../libs/Permissions'; import networkPropTypes from '../../components/networkPropTypes'; import {withNetwork} from '../../components/OnyxProvider'; +import * as App from '../../libs/actions/App'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes, withCurrentUserPersonalDetailsDefaultProps} from '../../components/withCurrentUserPersonalDetails'; import * as Policy from '../../libs/actions/Policy'; import policyMemberPropType from '../policyMemberPropType'; @@ -86,10 +86,7 @@ const defaultMenuItems = [ { translationKey: 'common.profile', icon: Expensicons.Profile, - action: () => { - DateUtils.updateTimezone(); - Navigation.navigate(ROUTES.SETTINGS_PROFILE); - }, + action: () => { App.openProfile(); }, }, { translationKey: 'common.preferences', diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js index b2583dc7f2bd..3dadbc80f92d 100755 --- a/src/pages/settings/Profile/ProfilePage.js +++ b/src/pages/settings/Profile/ProfilePage.js @@ -66,20 +66,19 @@ class ProfilePage extends Component { constructor(props) { super(props); - this.defaultAvatar = ReportUtils.getDefaultAvatar(this.props.currentUserPersonalDetails.login); - + const currentUserDetails = this.props.currentUserPersonalDetails || {}; this.state = { - firstName: this.props.currentUserPersonalDetails.firstName, + firstName: currentUserDetails.firstName || '', hasFirstNameError: false, - lastName: this.props.currentUserPersonalDetails.lastName, + lastName: currentUserDetails.lastName || '', hasLastNameError: false, - pronouns: this.props.currentUserPersonalDetails.pronouns, + pronouns: currentUserDetails.pronouns || '', hasPronounError: false, - hasSelfSelectedPronouns: !_.isEmpty(this.props.currentUserPersonalDetails.pronouns) && !this.props.currentUserPersonalDetails.pronouns.startsWith(CONST.PRONOUNS.PREFIX), - selectedTimezone: lodashGet(this.props.currentUserPersonalDetails, 'timezone.selected', CONST.DEFAULT_TIME_ZONE.selected), - isAutomaticTimezone: lodashGet(this.props.currentUserPersonalDetails, 'timezone.automatic', CONST.DEFAULT_TIME_ZONE.automatic), + hasSelfSelectedPronouns: !_.isEmpty(currentUserDetails.pronouns) && !currentUserDetails.pronouns.startsWith(CONST.PRONOUNS.PREFIX), + selectedTimezone: lodashGet(currentUserDetails, 'timezone.selected', CONST.DEFAULT_TIME_ZONE.selected), + isAutomaticTimezone: lodashGet(currentUserDetails, 'timezone.automatic', CONST.DEFAULT_TIME_ZONE.automatic), logins: this.getLogins(props.loginList), - avatar: {uri: lodashGet(this.props.currentUserPersonalDetails, 'avatar', ReportUtils.getDefaultAvatar(this.props.currentUserPersonalDetails.login))}, + avatar: {uri: currentUserDetails.avatar || ReportUtils.getDefaultAvatar(currentUserDetails.login)}, isAvatarChanged: false, }; @@ -88,6 +87,7 @@ class ProfilePage extends Component { this.updatePersonalDetails = this.updatePersonalDetails.bind(this); this.validateInputs = this.validateInputs.bind(this); this.updateAvatar = this.updateAvatar.bind(this); + this.deleteAvatar = this.deleteAvatar.bind(this); } componentDidUpdate(prevProps) { @@ -130,7 +130,7 @@ class ProfilePage extends Component { 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 !== this.props.currentUserPersonalDetails.login) && !_.isEmpty(logins[type]) + if ((login !== lodashGet(this.props.currentUserPersonalDetails, 'login')) && !_.isEmpty(logins[type]) && (logins[type].validatedDate || !currentLogin.validatedDate)) { return logins; } @@ -153,7 +153,15 @@ class ProfilePage extends Component { * @param {Object} avatar */ updateAvatar(avatar) { - this.setState({avatar: _.isUndefined(avatar) ? {uri: ReportUtils.getDefaultAvatar(this.props.currentUserPersonalDetails.login)} : avatar, isAvatarChanged: true}); + this.setState({avatar, isAvatarChanged: true}); + } + + /** + * Replaces the user's current avatar image with a default avatar. + */ + deleteAvatar() { + PersonalDetails.deleteAvatar(); + this.setState({avatar: {uri: ReportUtils.getDefaultAvatar(lodashGet(this.props.currentUserPersonalDetails, 'login'))}}); } /** @@ -165,27 +173,22 @@ class ProfilePage extends Component { } // Check if the user has modified their avatar - if ((this.props.currentUserPersonalDetails.avatar !== this.state.avatar.uri) && this.state.isAvatarChanged) { - // If the user removed their profile photo, replace it accordingly with the default avatar - if (this.state.avatar.uri.includes('/images/avatars/avatar')) { - PersonalDetails.deleteAvatar(this.state.avatar.uri); - } else { - PersonalDetails.setAvatar(this.state.avatar); - } + if ((lodashGet(this.props.currentUserPersonalDetails, 'avatar') !== this.state.avatar.uri) && this.state.isAvatarChanged) { + PersonalDetails.setAvatar(this.state.avatar); // Reset the changed state this.setState({isAvatarChanged: false}); } - PersonalDetails.setPersonalDetails({ - firstName: this.state.firstName.trim(), - lastName: this.state.lastName.trim(), - pronouns: this.state.pronouns.trim(), - timezone: { + PersonalDetails.updateProfile( + this.state.firstName.trim(), + this.state.lastName.trim(), + this.state.pronouns.trim(), + { automatic: this.state.isAutomaticTimezone, selected: this.state.selectedTimezone, }, - }, true); + ); } validateInputs() { @@ -208,12 +211,13 @@ class ProfilePage extends Component { })); // Disables button if none of the form values have changed - const isButtonDisabled = (this.props.currentUserPersonalDetails.firstName === this.state.firstName.trim()) - && (this.props.currentUserPersonalDetails.lastName === this.state.lastName.trim()) - && (this.props.currentUserPersonalDetails.timezone.selected === this.state.selectedTimezone) - && (this.props.currentUserPersonalDetails.timezone.automatic === this.state.isAutomaticTimezone) - && (this.props.currentUserPersonalDetails.pronouns === this.state.pronouns.trim()) - && (!this.state.isAvatarChanged || this.props.currentUserPersonalDetails.avatarUploading); + const currentUserDetails = this.props.currentUserPersonalDetails || {}; + const isButtonDisabled = (currentUserDetails.firstName === this.state.firstName.trim()) + && (currentUserDetails.lastName === this.state.lastName.trim()) + && (lodashGet(currentUserDetails, 'timezone.selected') === this.state.selectedTimezone) + && (lodashGet(currentUserDetails, 'timezone.automatic') === this.state.isAutomaticTimezone) + && (currentUserDetails.pronouns === this.state.pronouns.trim()) + && (!this.state.isAvatarChanged || currentUserDetails.avatarUploading); const pronounsPickerValue = this.state.hasSelfSelectedPronouns ? CONST.PRONOUNS.SELF_SELECT : this.state.pronouns; @@ -228,11 +232,11 @@ class ProfilePage extends Component { />