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

Show all Expensify logins in Contact Methods page & view each in new Contact Method Details page #15204

Merged
merged 64 commits into from
Mar 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
8d4b71e
New green brick indicator status
Beamanator Feb 16, 2023
8e2741e
New contact method details page
Beamanator Feb 16, 2023
af51025
Dynamically show all of a user's logins
Beamanator Feb 16, 2023
7f82353
Initial copy for contact methods page
Beamanator Feb 16, 2023
7889bae
Add new possible value for brickRoadIndicator
Beamanator Feb 16, 2023
4e8474b
Add key to array of items being rendered
Beamanator Feb 16, 2023
f097519
Populate contact method details page
Beamanator Feb 16, 2023
873bbc7
Merge branch 'main' of github.com:Expensify/App into beaman-showAllEx…
Beamanator Feb 21, 2023
5a1de67
New API call for deleting contact methods
Beamanator Feb 21, 2023
c2bd0ac
New function for clearing contact method errors
Beamanator Feb 21, 2023
8845a75
Show & connect remove button
Beamanator Feb 21, 2023
23fa444
Merge branch 'main' of github.com:Expensify/App into beaman-showAllEx…
Beamanator Feb 21, 2023
aeb85ff
Convert page to class for confirmation modal
Beamanator Feb 21, 2023
84cb98b
Fix hasPhoneNumberLogin check
Beamanator Feb 21, 2023
1773f39
Merge branch 'main' of github.com:Expensify/App into beaman-showAllEx…
Beamanator Feb 22, 2023
a9698fd
New UI for magic code input form
Beamanator Feb 22, 2023
dc63b43
Fix for if we just removed a contact method
Beamanator Feb 22, 2023
a893888
A few new contact translations
Beamanator Feb 22, 2023
e960125
New command for requesting magic code for contact method
Beamanator Feb 22, 2023
a0ffda2
Move pages so page transitions look correct
Beamanator Feb 22, 2023
32f443c
Add validate contact method button
Beamanator Feb 22, 2023
748f3db
New command to validate a secondary login
Beamanator Feb 22, 2023
c44f11f
Merge branch 'main' of github.com:Expensify/App into beaman-showAllEx…
Beamanator Feb 23, 2023
73b95c9
Add generic failure message for delete contact method
Beamanator Feb 23, 2023
3f955bb
Lint, style, function fixes
Beamanator Feb 23, 2023
ffd616c
Show green checkmark after sending validate code
Beamanator Feb 23, 2023
3371ca0
Fix error & validate command params
Beamanator Feb 23, 2023
8ed002c
Show resend validate code error messages
Beamanator Feb 23, 2023
86cbead
There's no debugger here, psh
Beamanator Feb 23, 2023
b0bb26b
Fix how errors show
Beamanator Feb 23, 2023
a01cb83
Show pending deleted state
Beamanator Feb 24, 2023
15a96c5
Merge branch 'main' of github.com:Expensify/App into beaman-showAllEx…
Beamanator Feb 24, 2023
ca6f3b0
Adding missing spanish translations
Beamanator Feb 24, 2023
f63f420
Clean up some offline UI
Beamanator Feb 24, 2023
319807e
Show offline UI in menu items
Beamanator Feb 24, 2023
8dbd5b8
Address lint issues
Beamanator Feb 24, 2023
ba3cd0c
Copy updates
Beamanator Feb 24, 2023
3324da8
Sneaky little semicolon, i found you
Beamanator Feb 24, 2023
346f8dc
Make function name make more sense
Beamanator Feb 27, 2023
96d6240
Merge branch 'main' of github.com:Expensify/App into beaman-showAllEx…
Beamanator Feb 27, 2023
3af0e1e
Clean up unused style
Beamanator Feb 27, 2023
7a883fc
Make status indicator color dynamic
Beamanator Feb 27, 2023
f17920a
Enable info indicator, remove unused large prop
Beamanator Feb 27, 2023
3b3e07a
Remove duplicate brick road status
Beamanator Feb 27, 2023
e20ea8f
Add login list utils for checking error / info status
Beamanator Feb 27, 2023
dfdaf8e
New util for getting loginList brick road color
Beamanator Feb 27, 2023
e43f2be
Integrate loginList brick road indicator
Beamanator Feb 27, 2023
3e542ee
Fix brick road indicator
Beamanator Feb 27, 2023
d36088b
Fix up util params and returns
Beamanator Mar 1, 2023
a9ce025
Fix up loginList proptypes
Beamanator Mar 1, 2023
eee93f7
Merge branch 'main' of github.com:Expensify/App into beaman-showAllEx…
Beamanator Mar 1, 2023
6e57549
Fix delete login online & offline logic
Beamanator Mar 1, 2023
f2a3b34
Make eslint happy
Beamanator Mar 1, 2023
96b9f16
Local eslint didn't catch this!!
Beamanator Mar 1, 2023
52b0133
Make horizontal padding a bit smaller
Beamanator Mar 1, 2023
860137d
Don't show remove button for default contact method
Beamanator Mar 1, 2023
72b1ed5
Merge branch 'main' of github.com:Expensify/App into beaman-showAllEx…
Beamanator Mar 2, 2023
2b6b1b2
Fix long description word break style
Beamanator Mar 2, 2023
b466891
Add more spacing between verify form and delete btn
Beamanator Mar 2, 2023
e927f49
Make delete button icon red
Beamanator Mar 2, 2023
170d379
remove unused prop
Beamanator Mar 2, 2023
c5fab55
Make status indicator bigger and top right
Beamanator Mar 2, 2023
49caf3f
Merge branch 'main' of github.com:Expensify/App into beaman-showAllEx…
Beamanator Mar 6, 2023
97ebafc
Address comments
Beamanator Mar 6, 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
2 changes: 2 additions & 0 deletions src/ROUTES.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export default {
SETTINGS_PERSONAL_DETAILS_DATE_OF_BIRTH: `${SETTINGS_PERSONAL_DETAILS}/date-of-birth`,
SETTINGS_PERSONAL_DETAILS_ADDRESS: `${SETTINGS_PERSONAL_DETAILS}/address`,
SETTINGS_CONTACT_METHODS,
SETTINGS_CONTACT_METHOD_DETAILS: `${SETTINGS_CONTACT_METHODS}/:contactMethod/details`,
getEditContactMethodRoute: contactMethod => `${SETTINGS_CONTACT_METHODS}/${encodeURIComponent(contactMethod)}/details`,
NEW_GROUP: 'new/group',
NEW_CHAT: 'new/chat',
REPORT,
Expand Down
49 changes: 35 additions & 14 deletions src/components/AvatarWithIndicator.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {policyPropTypes} from '../pages/workspace/withPolicy';
import walletTermsPropTypes from '../pages/EnablePayments/walletTermsPropTypes';
import * as PolicyUtils from '../libs/PolicyUtils';
import * as PaymentMethods from '../libs/actions/PaymentMethods';
import * as UserUtils from '../libs/UserUtils';
import themeColors from '../styles/themes/default';

const propTypes = {
/** URL for the avatar */
Expand All @@ -26,6 +28,8 @@ const propTypes = {
/** To show a tooltip on hover */
tooltipText: PropTypes.string,

/* Onyx Props */

/** The employee list of all policies (coming from Onyx) */
policiesMemberList: PropTypes.objectOf(policyMemberPropType),

Expand All @@ -43,6 +47,15 @@ const propTypes = {

/** Information about the user accepting the terms for payments */
walletTerms: walletTermsPropTypes,

/** Login list for the user that is signed in */
loginList: PropTypes.shape({
/** Date login was validated, used to show info indicator status */
validatedDate: PropTypes.string,

/** Field-specific server side errors keyed by microtime */
errorFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)),
}),
};

const defaultProps = {
Expand All @@ -54,45 +67,50 @@ const defaultProps = {
cardList: {},
userWallet: {},
walletTerms: {},
loginList: {},
};

const AvatarWithIndicator = (props) => {
const isLarge = props.size === 'large';
const indicatorStyles = [
styles.alignItemsCenter,
styles.justifyContentCenter,
isLarge ? styles.statusIndicatorLarge : styles.statusIndicator,
];

// If a policy was just deleted from Onyx, then Onyx will pass a null value to the props, and
// those should be cleaned out before doing any error checking
const cleanPolicies = _.pick(props.policies, policy => policy);
const cleanPolicyMembers = _.pick(props.policiesMemberList, member => member);

// All of the error-checking methods are put into an array. This is so that using _.some() will return
// early as soon as the first error is returned. This makes the error checking very efficient since
// we only care if a single error exists anywhere.
// All of the error & info-checking methods are put into an array. This is so that using _.some() will return
// early as soon as the first error / info condition is returned. This makes the checks very efficient since
// we only care if a single error / info condition exists anywhere.
const errorCheckingMethods = [
() => !_.isEmpty(props.userWallet.errors),
() => PaymentMethods.hasPaymentMethodError(props.bankAccountList, props.cardList),
() => _.some(cleanPolicies, PolicyUtils.hasPolicyError),
() => _.some(cleanPolicies, PolicyUtils.hasCustomUnitsError),
() => _.some(cleanPolicyMembers, PolicyUtils.hasPolicyMemberError),
() => UserUtils.hasLoginListError(props.loginList),

// Wallet term errors that are not caused by an IOU (we show the red brick indicator for those in the LHN instead)
() => !_.isEmpty(props.walletTerms.errors) && !props.walletTerms.chatReportID,
];
const shouldShowIndicator = _.some(errorCheckingMethods, errorCheckingMethod => errorCheckingMethod());
const infoCheckingMethods = [
() => UserUtils.hasLoginListInfo(props.loginList),
];
const shouldShowErrorIndicator = _.some(errorCheckingMethods, errorCheckingMethod => errorCheckingMethod());
const shouldShowInfoIndicator = !shouldShowErrorIndicator && _.some(infoCheckingMethods, infoCheckingMethod => infoCheckingMethod());

const indicatorColor = shouldShowErrorIndicator ? themeColors.danger : themeColors.success;
const indicatorStyles = [
styles.alignItemsCenter,
styles.justifyContentCenter,
styles.statusIndicator(indicatorColor),
];

return (
<View style={[isLarge ? styles.avatarLarge : styles.sidebarAvatar]}>
<View style={[styles.sidebarAvatar]}>
<Tooltip text={props.tooltipText}>
<Avatar
imageStyles={[isLarge ? styles.avatarLarge : null]}
source={props.source}
size={props.size}
/>
{shouldShowIndicator && (
{(shouldShowErrorIndicator || shouldShowInfoIndicator) && (
<View style={StyleSheet.flatten(indicatorStyles)} />
)}
</Tooltip>
Expand Down Expand Up @@ -123,4 +141,7 @@ export default withOnyx({
walletTerms: {
key: ONYXKEYS.WALLET_TERMS,
},
loginList: {
key: ONYXKEYS.LOGIN_LIST,
},
})(AvatarWithIndicator);
2 changes: 1 addition & 1 deletion src/components/MenuItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ const MenuItem = (props) => {
const descriptionTextStyle = StyleUtils.combineStyles([
styles.textLabelSupporting,
(props.icon ? styles.ml3 : undefined),
styles.breakAll,
styles.breakWord,
styles.lineHeightNormal,
], props.style);

Expand Down
16 changes: 16 additions & 0 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ export default {
youAppearToBeOffline: 'You appear to be offline.',
thisFeatureRequiresInternet: 'This feature requires an active internet connection to be used.',
areYouSure: 'Are you sure?',
verify: 'Verify',
yesContinue: 'Yes, continue',
zipCodeExample: 'e.g. 12345, 12345-1234, 12345 1234',
websiteExample: 'e.g. https://www.expensify.com',
},
Expand Down Expand Up @@ -341,6 +343,20 @@ export default {
contacts: {
contactMethod: 'Contact method',
contactMethods: 'Contact methods',
helpTextBeforeEmail: 'Add more ways for people to find you, and forward receipts to ',
helpTextAfterEmail: ' from multiple email addresses.',
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}`,
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',
removeAreYouSure: 'Are you sure you want to remove this contact method? This action cannot be undone.',
resendMagicCode: 'Resend magic code',
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.',
},
},
pronouns: {
coCos: 'Co / Cos',
Expand Down
16 changes: 16 additions & 0 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ export default {
youAppearToBeOffline: 'Parece que estás desconectado.',
thisFeatureRequiresInternet: 'Esta función requiere una conexión a Internet activa para ser utilizada.',
areYouSure: '¿Estás seguro?',
verify: 'Verifique',
yesContinue: 'Sí, Continuar',
zipCodeExample: 'p. ej. 12345, 12345-1234, 12345 1234',
websiteExample: 'p. ej. https://www.expensify.com',
},
Expand Down Expand Up @@ -341,6 +343,20 @@ export default {
contacts: {
contactMethod: 'Método de contacto',
contactMethods: 'Métodos de contacto',
helpTextBeforeEmail: 'Añade más formas de que la gente te encuentre y reenvía los recibos a ',
helpTextAfterEmail: ' desde varias direcciones de correo electrónico.',
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}`,
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',
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',
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.',
},
},
pronouns: {
coCos: 'Co / Cos',
Expand Down
23 changes: 23 additions & 0 deletions src/libs/ErrorUtils.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import _ from 'underscore';
import lodashGet from 'lodash/get';
import CONST from '../CONST';

/**
Expand Down Expand Up @@ -54,8 +55,30 @@ function getLatestErrorMessage(onyxData) {
.value();
}

/**
* @param {Object} onyxData
* @param {Object} onyxData.errorFields
* @param {String} fieldName
* @returns {Object}
*/
function getLatestErrorField(onyxData, fieldName) {
const errorsForField = lodashGet(onyxData, ['errorFields', fieldName], {});

if (_.isEmpty(errorsForField)) {
return {};
}
return _.chain(errorsForField)
.keys()
.sortBy()
.reverse()
.map(key => ({[key]: errorsForField[key]}))
.first()
.value();
}

export {
// eslint-disable-next-line import/prefer-default-export
getAuthenticateErrorMessage,
getLatestErrorMessage,
getLatestErrorField,
};
21 changes: 14 additions & 7 deletions src/libs/Navigation/AppNavigator/ModalStackNavigators.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,20 @@ const SettingsModalStackNavigator = createModalStackNavigator([
},
name: 'Settings_Profile',
},
{
getComponent: () => {
const SettingsContactMethodDetailsPage = require('../../../pages/settings/Profile/Contacts/ContactMethodDetailsPage').default;
return SettingsContactMethodDetailsPage;
},
name: 'Settings_ContactMethodDetails',
},
{
getComponent: () => {
const SettingsContactMethodsPage = require('../../../pages/settings/Profile/Contacts/ContactMethodsPage').default;
return SettingsContactMethodsPage;
},
name: 'Settings_ContactMethods',
},
{
getComponent: () => {
const SettingsPronounsPage = require('../../../pages/settings/Profile/PronounsPage').default;
Expand Down Expand Up @@ -273,13 +287,6 @@ const SettingsModalStackNavigator = createModalStackNavigator([
},
name: 'Settings_PersonalDetails_Address',
},
{
getComponent: () => {
const SettingsContactMethodsPage = require('../../../pages/settings/Profile/Contacts/ContactMethodsPage').default;
return SettingsContactMethodsPage;
},
name: 'Settings_ContactMethods',
},
{
getComponent: () => {
const SettingsAddSecondaryLoginPage = require('../../../pages/settings/Profile/Contacts/AddSecondaryLoginPage').default;
Expand Down
3 changes: 3 additions & 0 deletions src/libs/Navigation/linkingConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ export default {
path: ROUTES.SETTINGS_CONTACT_METHODS,
exact: true,
},
Settings_ContactMethodDetails: {
path: ROUTES.SETTINGS_CONTACT_METHOD_DETAILS,
},
Settings_Add_Secondary_Login: {
path: ROUTES.SETTINGS_ADD_LOGIN,
},
Expand Down
68 changes: 68 additions & 0 deletions src/libs/UserUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import _ from 'underscore';
import lodashGet from 'lodash/get';
import CONST from '../CONST';

/**
* Searches through given loginList for any contact method / login with an error.
*
* Example that should return false:
* {{
* test@test.com: {
* errorFields: {
* validateCodeSent: null
* }
* }
* }}
*
* Example that should return true:
* {{
* test@test.com: {
* errorFields: {
* validateCodeSent: { 18092081290: 'An error' }
* }
* }
* }}
*
* @param {Object} loginList
* @param {Object} loginList.errorFields
* @returns {Boolean}
*/
function hasLoginListError(loginList) {
return _.some(loginList, login => _.some(lodashGet(login, 'errorFields', {}), field => !_.isEmpty(field)));
}

/**
* Searches through given loginList for any contact method / login that requires
* an Info brick road status indicator. Currently this only applies if the user
* has an unvalidated contact method.
*
* @param {Object} loginList
* @param {String} loginList.validatedDate
* @returns {Boolean}
*/
function hasLoginListInfo(loginList) {
return _.some(loginList, login => _.isEmpty(login.validatedDate));
}

/**
* Gets the appropriate brick road indicator status for a given loginList.
* Error status is higher priority, so we check for that first.
*
* @param {Object} loginList
* @returns {String}
*/
function getLoginListBrickRoadIndicator(loginList) {
if (hasLoginListError(loginList)) {
return CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR;
}
if (hasLoginListInfo(loginList)) {
return CONST.BRICK_ROAD_INDICATOR_STATUS.INFO;
}
return '';
}

export {
hasLoginListError,
hasLoginListInfo,
getLoginListBrickRoadIndicator,
};
Loading
Loading