Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
faea1c4
New command OpenProfilePage
Beamanator Jul 5, 2022
6057115
New command UpdateProfile
Beamanator Jul 5, 2022
50ba9ec
New command DeleteUserAvatar (WIP)
Beamanator Jul 5, 2022
d5836f2
Merge branch 'main' of github.com:Expensify/App into beaman-refactorP…
Beamanator Jul 15, 2022
fd71758
Remove 'page' from command
Beamanator Jul 15, 2022
9f8d7f0
Clean up logic for deleting user avatar
Beamanator Jul 15, 2022
0472768
Clean up logic and lint
Beamanator Jul 15, 2022
35a24ed
Fix new openProfile function name
Beamanator Jul 15, 2022
70ce913
Use onyx consts, stringify data
Beamanator Jul 15, 2022
f8beb46
Stringify data sent to server & fix default avatar
Beamanator Jul 15, 2022
816ed7a
Merge branch 'main' of github.com:Expensify/App into beaman-refactorP…
Beamanator Jul 18, 2022
3ebc476
Clean up logic getting my personal details
Beamanator Jul 18, 2022
1c23ade
Update state if avatar prop changes
Beamanator Jul 18, 2022
0a05600
Merge branch 'main' of github.com:Expensify/App into beaman-refactorP…
Beamanator Jul 19, 2022
259e877
Remove unused import
Beamanator Jul 19, 2022
0ec78f2
Remove dupe import
Beamanator Jul 19, 2022
4821dec
Merge branch 'main' of github.com:Expensify/App into beaman-refactorP…
Beamanator Jul 21, 2022
9f51973
Update default avatar in state better
Beamanator Jul 21, 2022
74935e2
Merge branch 'main' of github.com:Expensify/App into beaman-refactorP…
Beamanator Jul 26, 2022
f6736b1
Merge branch 'main' of github.com:Expensify/App into beaman-refactorP…
Beamanator Jul 27, 2022
8b6dd2a
Clean up lodashGets, adding defaults where needed
Beamanator Jul 28, 2022
67acb89
Fix deleteAvatar, rename
Beamanator Jul 28, 2022
4aeee66
Fix logic to keep timezone.automatic same
Beamanator Jul 28, 2022
3792cc8
Update displayName in onyx when name changes
Beamanator Jul 29, 2022
21655be
Merge branch 'main' of github.com:Expensify/App into beaman-refactorP…
Beamanator Jul 29, 2022
d37051b
Send all personal details as own params
Beamanator Jul 29, 2022
c2f5de5
Merge branch 'main' of github.com:Expensify/App into beaman-refactorP…
Beamanator Aug 1, 2022
40b05a4
Add back important check
Beamanator Aug 1, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions src/libs/actions/App.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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', '');
},
});

Expand All @@ -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,
Expand Down Expand Up @@ -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),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed with Alex in DM, I think this function does not work as expected with the VPNs and it does not set you to the timezone of the area. However, that is how we have it now and I would argue it is better as in the chat you want others to see your actual timezone and not the VPN timezone.

};
Comment thread
mountiny marked this conversation as resolved.

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();
Expand All @@ -219,5 +261,6 @@ export {
getAppData,
fixAccountAndReloadData,
setUpPoliciesAndNavigate,
openProfile,
openApp,
};
73 changes: 66 additions & 7 deletions src/libs/actions/PersonalDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}),
Comment thread
Beamanator marked this conversation as resolved.
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,
}),
Comment on lines +284 to +287

@Beamanator Beamanator Jul 29, 2022

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI this will need to be added in Web-E so that all clients get updated with this displayName, I plan to add this requirement in this issue: https://github.com/Expensify/Expensify/issues/220321

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, but we should not get any problems when sending this. It will get updated for other users when their refresh, but it is not included in the Pusher update you saying?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right right exactly @mountiny - nothing will break, only the client making the change will get these updates automatically, others will get it after refresh 👍

},
},
}],
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
*/
Expand Down Expand Up @@ -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,
Comment thread
Beamanator marked this conversation as resolved.
},
},
}],
failureData: [{
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.PERSONAL_DETAILS,
value: {
[currentUserEmail]: {
avatar: personalDetails[currentUserEmail].avatar,
},
},
}],
});
}

export {
Expand All @@ -315,4 +373,5 @@ export {
openIOUModalPage,
getMaxCharacterError,
extractFirstAndLastNameFromAvailableDetails,
updateProfile,
};
7 changes: 2 additions & 5 deletions src/pages/settings/InitialSettingsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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',
Expand Down
68 changes: 36 additions & 32 deletions src/pages/settings/Profile/ProfilePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand All @@ -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) {
Expand Down Expand Up @@ -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;
}
Expand All @@ -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'))}});
}

/**
Expand All @@ -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);
);
Comment on lines +183 to +191

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This tiny change gave rise to #10215

Note: The main issue is opacity being incorrect, not the Growl being missing

}

validateInputs() {
Expand All @@ -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;

Expand All @@ -228,11 +232,11 @@ class ProfilePage extends Component {
/>
<ScrollView style={styles.flex1} contentContainerStyle={styles.p5}>
<AvatarWithImagePicker
isUploading={this.props.currentUserPersonalDetails.avatarUploading}
isUploading={currentUserDetails.avatarUploading}
isUsingDefaultAvatar={this.state.avatar.uri.includes('/images/avatars/avatar')}
avatarURL={this.state.avatar.uri}
onImageSelected={this.updateAvatar}
onImageRemoved={this.updateAvatar}
onImageRemoved={this.deleteAvatar}
anchorPosition={styles.createMenuPositionProfile}
size={CONST.AVATAR_SIZE.LARGE}
/>
Expand Down