Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to set a contact method your default contact method #16750

Merged
merged 40 commits into from
May 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
dcb89be
Connect to SetContactMethodAsDefault
Beamanator Mar 30, 2023
8c20ecb
New copy for generic error
Beamanator Apr 4, 2023
692dbf5
Connect new set default button
Beamanator Apr 4, 2023
4d11e0a
Merge branch 'main' of github.com:Expensify/App into beaman-addSetCon…
Beamanator Apr 5, 2023
c70731a
Add function docs
Beamanator Apr 5, 2023
8d333b7
New icon for set as default
Beamanator Apr 5, 2023
1bf0be7
Add set default translation & offline UX
Beamanator Apr 6, 2023
f5035d5
Use more appropriate icon
Beamanator Apr 6, 2023
62c25d5
Fix spacing & var
Beamanator Apr 6, 2023
7fae21c
Merge branch 'main' of github.com:Expensify/App into beaman-addSetCon…
Beamanator Apr 6, 2023
33b5f76
Hide Set as Default if security group restricts it
Beamanator Apr 6, 2023
003ca94
Add new onyx key proptypes
Beamanator Apr 6, 2023
106b008
Dont show set default if not validated
Beamanator Apr 7, 2023
a690c29
Merge branch 'main' of github.com:Expensify/App into beaman-addSetCon…
Beamanator Apr 7, 2023
2aee36c
Simplify logic a bit
Beamanator Apr 7, 2023
3f1055a
Add Set as Default page for non-passwordless
Beamanator Apr 7, 2023
0fb43c7
Allow password to be sent to command
Beamanator Apr 10, 2023
12ec0eb
Remove unnecessary new file, oops
Beamanator Apr 10, 2023
4aa6e7f
Only show set default if on passwordless
Beamanator Apr 11, 2023
f81cee1
Merge branch 'main' of github.com:Expensify/App into beaman-addSetCon…
Beamanator Apr 11, 2023
898635e
Merge branch 'main' of github.com:Expensify/App into beaman-addSetCon…
Beamanator Apr 13, 2023
92f928b
A few lint fixes
Beamanator Apr 13, 2023
a0ead11
Remove unused style
Beamanator Apr 13, 2023
06fb162
Merge remote-tracking branch 'origin/main' into beaman-addSetContactM…
cristipaval Apr 13, 2023
cc23f2c
Merge branch 'main' of github.com:Expensify/App into beaman-addSetCon…
Beamanator Apr 17, 2023
2bc9dd2
Merge branch 'beaman-addSetContactMethodAsDefault' of github.com:Expe…
Beamanator Apr 17, 2023
7d23b51
Merge branch 'main' of github.com:Expensify/App into beaman-addSetCon…
Beamanator Apr 21, 2023
79c2a46
Set new default contact method in personalDetails
Beamanator Apr 26, 2023
8a0433d
Merge branch 'main' of github.com:Expensify/App into beaman-addSetCon…
Beamanator Apr 26, 2023
b0d3ad8
Merge branch 'main' of github.com:Expensify/App into beaman-addSetCon…
Beamanator Apr 28, 2023
a26b278
Update personal details w/ new default cm
Beamanator May 1, 2023
3173b55
Merge branch 'main' of github.com:Expensify/App into beaman-addSetCon…
Beamanator May 8, 2023
ec9918e
Update to new constant location
Beamanator May 8, 2023
bbe4197
Update other actions to new onyx constant location
Beamanator May 8, 2023
6d15fdf
Remove passwordless references
Beamanator May 8, 2023
df51b7e
Merge branch 'main' of github.com:Expensify/App into beaman-addSetCon…
Beamanator May 10, 2023
ab5862a
prettify files
Beamanator May 10, 2023
d54ca19
Remove unnecessary fragment
Beamanator May 10, 2023
0c1fc10
Refactor to put all change logic into 1 fn
Beamanator May 10, 2023
2fea0f3
linter told me to do this
Beamanator May 10, 2023
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
4 changes: 4 additions & 0 deletions src/ONYXKEYS.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export default {
REPORT_DRAFT_COMMENT_NUMBER_OF_LINES: 'reportDraftCommentNumberOfLines_',
REPORT_IS_COMPOSER_FULL_SIZE: 'reportIsComposerFullSize_',
REPORT_USER_IS_TYPING: 'reportUserIsTyping_',
SECURITY_GROUP: 'securityGroup_',
TRANSACTION: 'transactions_',
},

Expand Down Expand Up @@ -212,4 +213,7 @@ export default {

// Whether the auth token is valid
IS_TOKEN_VALID: 'isTokenValid',

// A map of the user's security group IDs they belong to in specific domains
MY_DOMAIN_SECURITY_GROUPS: 'myDomainSecurityGroups',
};
2 changes: 2 additions & 0 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ export default {
pleaseVerify: 'Please verify this contact method',
getInTouch: "Whenever we need to get in touch with you, we'll use this contact method.",
enterMagicCode: ({contactMethod}) => `Please enter the magic code sent to ${contactMethod}`,
setAsDefault: 'Set as default',
yourDefaultContactMethod:
'This is your current default contact method. You will not be able to delete this contact method until you set an alternative default by selecting another contact method and pressing “Set as default”.',
removeContactMethod: 'Remove contact method',
Expand All @@ -389,6 +390,7 @@ export default {
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.',
setDefaultContactMethod: 'Failed to set a new default 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',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ export default {
pleaseVerify: 'Por favor, verifica este método de contacto',
getInTouch: 'Utilizaremos este método de contacto cuando necesitemos contactarte.',
enterMagicCode: ({contactMethod}) => `Por favor, introduce el código mágico enviado a ${contactMethod}`,
setAsDefault: 'Establecer como predeterminado',
yourDefaultContactMethod:
'Este es tu método de contacto predeterminado. No podrás eliminarlo hasta que añadas otro método de contacto y lo marques como predeterminado pulsando "Establecer como predeterminado".',
removeContactMethod: 'Eliminar método de contacto',
Expand All @@ -388,6 +389,7 @@ export default {
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 este método de contacto. Por favor, contacta con Concierge para obtener ayuda.',
setDefaultContactMethod: 'No se pudo establecer un nuevo método de contacto predeterminado. 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',
Expand Down
108 changes: 108 additions & 0 deletions src/libs/actions/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,27 @@ import * as Report from './Report';
import * as ReportActionsUtils from '../ReportActionsUtils';
import DateUtils from '../DateUtils';
import * as Session from './Session';
import * as PersonalDetails from './PersonalDetails';

let currentUserAccountID = '';
let currentEmail = '';
Onyx.connect({
key: ONYXKEYS.SESSION,
callback: (val) => {
currentUserAccountID = lodashGet(val, 'accountID', '');
currentEmail = lodashGet(val, 'email', '');
},
});

let myPersonalDetails = {};
Onyx.connect({
key: ONYXKEYS.PERSONAL_DETAILS,
callback: (val) => {
if (!val || !currentEmail) {
return;
}

myPersonalDetails = val[currentEmail];
},
});

Expand Down Expand Up @@ -760,6 +775,98 @@ function generateStatementPDF(period) {
);
}

/**
* Sets a contact method / secondary login as the user's "Default" contact method.
*
* @param {String} newDefaultContactMethod
*/
function setContactMethodAsDefault(newDefaultContactMethod) {
const oldDefaultContactMethod = currentEmail;
const optimisticData = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.SESSION,
value: {
email: newDefaultContactMethod,
},
},
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.LOGIN_LIST,
value: {
[newDefaultContactMethod]: {
pendingFields: {
defaultLogin: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
errorFields: {
defaultLogin: null,
},
},
},
},
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.PERSONAL_DETAILS,
value: {
[newDefaultContactMethod]: {
...myPersonalDetails,
login: newDefaultContactMethod,
displayName: PersonalDetails.getDisplayName(newDefaultContactMethod, myPersonalDetails),
},
[oldDefaultContactMethod]: null,
},
},
];
const successData = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.LOGIN_LIST,
value: {
[newDefaultContactMethod]: {
pendingFields: {
defaultLogin: null,
},
},
},
},
];
const failureData = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.SESSION,
value: {
email: oldDefaultContactMethod,
},
},
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.LOGIN_LIST,
value: {
[newDefaultContactMethod]: {
pendingFields: {
defaultLogin: null,
},
errorFields: {
defaultLogin: {
[DateUtils.getMicroseconds()]: Localize.translateLocal('contacts.genericFailureMessages.setDefaultContactMethod'),
},
},
},
},
},
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.PERSONAL_DETAILS,
value: {
[newDefaultContactMethod]: null,
[oldDefaultContactMethod]: {...myPersonalDetails},
},
},
];
API.write('SetContactMethodAsDefault', {partnerUserID: newDefaultContactMethod}, {optimisticData, successData, failureData});
Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS);
}

export {
updatePassword,
closeAccount,
Expand All @@ -784,4 +891,5 @@ export {
deletePaypalMeAddress,
addPaypalMeAddress,
updateChatPriorityMode,
setContactMethodAsDefault,
};
84 changes: 81 additions & 3 deletions src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ const propTypes = {
email: PropTypes.string.isRequired,
}),

/** User's security group IDs by domain */
myDomainSecurityGroups: PropTypes.objectOf(PropTypes.string),

/** All of the user's security groups and their settings */
securityGroups: PropTypes.shape({
hasRestrictedPrimaryLogin: PropTypes.bool,
}),

/** Route params */
route: PropTypes.shape({
params: PropTypes.shape({
Expand All @@ -72,6 +80,8 @@ const defaultProps = {
session: {
email: null,
},
myDomainSecurityGroups: {},
securityGroups: {},
route: {
params: {
contactMethod: '',
Expand All @@ -89,6 +99,7 @@ class ContactMethodDetailsPage extends Component {
this.resendValidateCode = this.resendValidateCode.bind(this);
this.getContactMethod = this.getContactMethod.bind(this);
this.validateAndSubmitCode = this.validateAndSubmitCode.bind(this);
this.setAsDefault = this.setAsDefault.bind(this);

this.state = {
formError: '',
Expand Down Expand Up @@ -117,6 +128,47 @@ class ContactMethodDetailsPage extends Component {
return decodeURIComponent(lodashGet(this.props.route, 'params.contactMethod'));
}

/**
* Attempt to set this contact method as user's "Default contact method"
*/
setAsDefault() {
User.setContactMethodAsDefault(this.getContactMethod());
}

/**
* Checks if the user is allowed to change their default contact method. This should only be allowed if:
* 1. The viewed contact method is not already their default contact method
* 2. The viewed contact method is validated
* 3. If the user is on a private domain, their security group must allow primary login switching
*
* @returns {Boolean}
*/
canChangeDefaultContactMethod() {
const contactMethod = this.getContactMethod();
const loginData = lodashGet(this.props.loginList, contactMethod, {});
const isDefaultContactMethod = this.props.session.email === loginData.partnerUserID;

// Cannot set this contact method as default if:
// 1. This contact method is already their default
// 2. This contact method is not validated
if (isDefaultContactMethod || !loginData.validatedDate) {
return false;
}

const domainName = Str.extractEmailDomain(this.props.session.email);
const primaryDomainSecurityGroupID = lodashGet(this.props.myDomainSecurityGroups, domainName);

// If there's no security group associated with the user for the primary domain,
// default to allowing the user to change their default contact method.
if (!primaryDomainSecurityGroupID) {
return true;
}

// Allow user to change their default contact method if they don't have a security group OR if their security group
// does NOT restrict primary login switching.
return !lodashGet(this.props.securityGroups, [`${ONYXKEYS.COLLECTION.SECURITY_GROUP}${primaryDomainSecurityGroupID}`, 'hasRestrictedPrimaryLogin'], false);
}

/**
* Deletes the contact method if it has errors. Otherwise, it shows the confirmation alert and deletes it only if the user confirms.
*/
Expand Down Expand Up @@ -168,7 +220,7 @@ class ContactMethodDetailsPage extends Component {
render() {
const contactMethod = this.getContactMethod();

// replacing spaces with "hard spaces" to prevent breaking the number
// Replacing spaces with "hard spaces" to prevent breaking the number
const formattedContactMethod = Str.isSMSLogin(contactMethod) ? this.props.formatPhoneNumber(contactMethod).replace(/ /g, '\u00A0') : contactMethod;

const loginData = this.props.loginList[contactMethod];
Expand Down Expand Up @@ -263,8 +315,28 @@ class ContactMethodDetailsPage extends Component {
</OfflineWithFeedback>
</View>
)}
{this.canChangeDefaultContactMethod() ? (
<OfflineWithFeedback
errors={ErrorUtils.getLatestErrorField(loginData, 'defaultLogin')}
errorRowStyles={[styles.ml8, styles.mr5]}
onClose={() => User.clearContactMethodErrors(contactMethod, 'defaultLogin')}
>
<MenuItem
title={this.props.translate('contacts.setAsDefault')}
icon={Expensicons.Profile}
onPress={this.setAsDefault}
/>
</OfflineWithFeedback>
) : null}
{isDefaultContactMethod ? (
<Text style={[styles.ph5, styles.mv3]}>{this.props.translate('contacts.yourDefaultContactMethod')}</Text>
<OfflineWithFeedback
pendingAction={lodashGet(loginData, 'pendingFields.defaultLogin', null)}
errors={ErrorUtils.getLatestErrorField(loginData, 'defaultLogin')}
errorRowStyles={[styles.ml8, styles.mr5]}
onClose={() => User.clearContactMethodErrors(contactMethod, 'defaultLogin')}
>
<Text style={[styles.ph5, styles.mv3]}>{this.props.translate('contacts.yourDefaultContactMethod')}</Text>
</OfflineWithFeedback>
) : (
<OfflineWithFeedback
pendingAction={lodashGet(loginData, 'pendingFields.deletedLogin', null)}
Expand All @@ -276,7 +348,7 @@ class ContactMethodDetailsPage extends Component {
title={this.props.translate('common.remove')}
icon={Expensicons.Trashcan}
iconFill={themeColors.danger}
onPress={this.deleteContactMethod}
onPress={() => this.toggleDeleteModal(true)}
/>
</OfflineWithFeedback>
)}
Expand All @@ -298,5 +370,11 @@ export default compose(
session: {
key: ONYXKEYS.SESSION,
},
myDomainSecurityGroups: {
key: ONYXKEYS.MY_DOMAIN_SECURITY_GROUPS,
},
securityGroups: {
key: `${ONYXKEYS.COLLECTION.SECURITY_GROUP}`,
},
}),
)(ContactMethodDetailsPage);