diff --git a/src/components/AvatarWithIndicator.js b/src/components/AvatarWithIndicator.js
index aa37769156e3..765ca641a3f4 100644
--- a/src/components/AvatarWithIndicator.js
+++ b/src/components/AvatarWithIndicator.js
@@ -1,126 +1,66 @@
-import React, {PureComponent} from 'react';
-import {
- View, StyleSheet, Animated,
-} from 'react-native';
+import _ from 'underscore';
+import React from 'react';
+import {StyleSheet, View} from 'react-native';
import PropTypes from 'prop-types';
+import {withOnyx} from 'react-native-onyx';
import Avatar from './Avatar';
-import themeColors from '../styles/themes/default';
import styles from '../styles/styles';
-import Icon from './Icon';
-import * as Expensicons from './Icon/Expensicons';
-import SpinningIndicatorAnimation from '../styles/animation/SpinningIndicatorAnimation';
import Tooltip from './Tooltip';
-import withLocalize, {withLocalizePropTypes} from './withLocalize';
+import ONYXKEYS from '../ONYXKEYS';
+import policyMemberPropType from '../pages/policyMemberPropType';
+import * as Policy from '../libs/actions/Policy';
const propTypes = {
- /** Is user active? */
- isActive: PropTypes.bool,
-
/** URL for the avatar */
source: PropTypes.string.isRequired,
/** Avatar size */
size: PropTypes.string,
- // Whether we show the sync indicator
- isSyncing: PropTypes.bool,
-
/** To show a tooltip on hover */
tooltipText: PropTypes.string,
- ...withLocalizePropTypes,
+ /** The employee list of all policies (coming from Onyx) */
+ policiesMemberList: PropTypes.objectOf(policyMemberPropType),
};
const defaultProps = {
- isActive: false,
size: 'default',
- isSyncing: false,
tooltipText: '',
+ policiesMemberList: {},
};
-class AvatarWithIndicator extends PureComponent {
- constructor(props) {
- super(props);
-
- this.animation = new SpinningIndicatorAnimation();
- }
-
- componentDidMount() {
- if (!this.props.isSyncing) {
- return;
- }
-
- this.animation.start();
- }
-
- componentDidUpdate(prevProps) {
- if (!prevProps.isSyncing && this.props.isSyncing) {
- this.animation.start();
- } else if (prevProps.isSyncing && !this.props.isSyncing) {
- this.animation.stop();
- }
- }
-
- componentWillUnmount() {
- this.animation.stop();
- }
-
- /**
- * Returns user status as text
- *
- * @returns {String}
- */
- userStatus() {
- if (this.props.isSyncing) {
- return this.props.translate('profilePage.syncing');
- }
-
- if (this.props.isActive) {
- return this.props.translate('profilePage.online');
- }
-
- if (!this.props.isActive) {
- return this.props.translate('profilePage.offline');
- }
- }
-
- render() {
- const indicatorStyles = [
- styles.alignItemsCenter,
- styles.justifyContentCenter,
- this.props.size === 'large' ? styles.statusIndicatorLarge : styles.statusIndicator,
- this.props.isActive ? styles.statusIndicatorOnline : styles.statusIndicatorOffline,
- this.animation.getSyncingStyles(),
- ];
-
- return (
-
-
-
-
-
-
- {this.props.isSyncing && (
-
- )}
-
-
-
- );
- }
-}
+const AvatarWithIndicator = (props) => {
+ const isLarge = props.size === 'large';
+ const indicatorStyles = [
+ styles.alignItemsCenter,
+ styles.justifyContentCenter,
+ isLarge ? styles.statusIndicatorLarge : styles.statusIndicator,
+ ];
+
+ const hasPolicyMemberError = _.some(props.policiesMemberList, policyMembers => Policy.hasPolicyMemberError(policyMembers));
+ return (
+
+
+
+ {hasPolicyMemberError && (
+
+ )}
+
+
+ );
+};
AvatarWithIndicator.defaultProps = defaultProps;
AvatarWithIndicator.propTypes = propTypes;
-export default withLocalize(AvatarWithIndicator);
+AvatarWithIndicator.displayName = 'AvatarWithIndicator';
+
+export default withOnyx({
+ policiesMemberList: {
+ key: ONYXKEYS.COLLECTION.POLICY_MEMBER_LIST,
+ },
+})(AvatarWithIndicator);
diff --git a/src/components/OfflineWithFeedback.js b/src/components/OfflineWithFeedback.js
index 196a4da3d403..2422e4745b73 100644
--- a/src/components/OfflineWithFeedback.js
+++ b/src/components/OfflineWithFeedback.js
@@ -124,6 +124,7 @@ const OfflineWithFeedback = (props) => {
OfflineWithFeedback.propTypes = propTypes;
OfflineWithFeedback.defaultProps = defaultProps;
+OfflineWithFeedback.displayName = 'OfflineWithFeedback';
export default compose(
withLocalize,
diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js
index 40b96e408565..11fd7f41e493 100644
--- a/src/libs/actions/Policy.js
+++ b/src/libs/actions/Policy.js
@@ -540,7 +540,34 @@ function subscribeToPolicyEvents() {
}
/**
- * Checks if we have any errors stored within the POLICY_MEMBER_LIST. Determines whether we should show a red brick road error or not
+ * Removes an error after trying to delete a member
+ *
+ * @param {String} policyID
+ * @param {String} memberEmail
+ */
+function clearDeleteMemberError(policyID, memberEmail) {
+ Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_MEMBER_LIST}${policyID}`, {
+ [memberEmail]: {
+ pendingAction: null,
+ errors: null,
+ },
+ });
+}
+
+/**
+ * Removes an error after trying to add a member
+ *
+ * @param {String} policyID
+ * @param {String} memberEmail
+ */
+function clearAddMemberError(policyID, memberEmail) {
+ Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_MEMBER_LIST}${policyID}`, {
+ [memberEmail]: null,
+ });
+}
+
+/**
+* Checks if we have any errors stored within the POLICY_MEMBER_LIST. Determines whether we should show a red brick road error or not
* Data structure: {email: {role:'bla', errors: []}, email2: {role:'bla', errors: [{1231312313: 'Unable to do X'}]}, ...}
* @param {Object} policyMemberList
* @returns {Boolean}
@@ -567,5 +594,7 @@ export {
setCustomUnitRate,
updateLastAccessedWorkspace,
subscribeToPolicyEvents,
+ clearDeleteMemberError,
+ clearAddMemberError,
hasPolicyMemberError,
};
diff --git a/src/pages/policyMemberPropType.js b/src/pages/policyMemberPropType.js
index 0e5c39e02369..22a4d355fbfb 100644
--- a/src/pages/policyMemberPropType.js
+++ b/src/pages/policyMemberPropType.js
@@ -9,4 +9,7 @@ export default PropTypes.shape({
* {: 'error message', : 'error message 2'}
*/
errors: PropTypes.objectOf(PropTypes.string),
+
+ /** Is this action pending? */
+ pendingAction: PropTypes.string,
});
diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js
index 7686c5c3b399..35b21c8d8894 100755
--- a/src/pages/settings/InitialSettingsPage.js
+++ b/src/pages/settings/InitialSettingsPage.js
@@ -10,7 +10,8 @@ import themeColors from '../../styles/themes/default';
import Text from '../../components/Text';
import * as Session from '../../libs/actions/Session';
import ONYXKEYS from '../../ONYXKEYS';
-import AvatarWithIndicator from '../../components/AvatarWithIndicator';
+import Tooltip from '../../components/Tooltip';
+import Avatar from '../../components/Avatar';
import HeaderWithCloseButton from '../../components/HeaderWithCloseButton';
import Navigation from '../../libs/Navigation/Navigation';
import * as Expensicons from '../../components/Icon/Expensicons';
@@ -21,8 +22,6 @@ import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize
import compose from '../../libs/compose';
import CONST from '../../CONST';
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';
@@ -31,9 +30,6 @@ import policyMemberPropType from '../policyMemberPropType';
const propTypes = {
/* Onyx Props */
- /** Information about the network */
- network: networkPropTypes.isRequired,
-
/** The session of the logged in person */
session: PropTypes.shape({
/** Email of the logged in person */
@@ -156,12 +152,13 @@ const InitialSettingsPage = (props) => {
-
+
+
+
@@ -211,7 +208,6 @@ InitialSettingsPage.displayName = 'InitialSettingsPage';
export default compose(
withLocalize,
- withNetwork(),
withCurrentUserPersonalDetails,
withOnyx({
session: {
diff --git a/src/pages/workspace/WorkspaceInitialPage.js b/src/pages/workspace/WorkspaceInitialPage.js
index 9cb442edb586..eb0c60ab9703 100644
--- a/src/pages/workspace/WorkspaceInitialPage.js
+++ b/src/pages/workspace/WorkspaceInitialPage.js
@@ -1,6 +1,8 @@
import _ from 'underscore';
import React from 'react';
import {View, ScrollView, Pressable} from 'react-native';
+import {withOnyx} from 'react-native-onyx';
+import PropTypes from 'prop-types';
import Navigation from '../../libs/Navigation/Navigation';
import ROUTES from '../../ROUTES';
import styles from '../../styles/styles';
@@ -20,13 +22,21 @@ import FullScreenLoadingIndicator from '../../components/FullscreenLoadingIndica
import withFullPolicy, {fullPolicyPropTypes, fullPolicyDefaultProps} from './withFullPolicy';
import * as PolicyActions from '../../libs/actions/Policy';
import CONST from '../../CONST';
+import ONYXKEYS from '../../ONYXKEYS';
+import policyMemberPropType from '../policyMemberPropType';
const propTypes = {
...fullPolicyPropTypes,
...withLocalizePropTypes,
+
+ /** The employee list of this policy (coming from Onyx) */
+ policyMemberList: PropTypes.objectOf(policyMemberPropType),
};
-const defaultProps = fullPolicyDefaultProps;
+const defaultProps = {
+ ...fullPolicyDefaultProps,
+ policyMemberList: {},
+};
class WorkspaceInitialPage extends React.Component {
constructor(props) {
@@ -70,6 +80,7 @@ class WorkspaceInitialPage extends React.Component {
return ;
}
+ const hasMembersError = PolicyActions.hasPolicyMemberError(this.props.policyMemberList);
const menuItems = [
{
translationKey: 'workspace.common.settings',
@@ -105,6 +116,7 @@ class WorkspaceInitialPage extends React.Component {
translationKey: 'workspace.common.members',
icon: Expensicons.Users,
action: () => Navigation.navigate(ROUTES.getWorkspaceMembersRoute(policy.id)),
+ error: hasMembersError,
},
{
translationKey: 'workspace.common.bankAccount',
@@ -202,6 +214,7 @@ class WorkspaceInitialPage extends React.Component {
iconRight={item.iconRight}
onPress={() => item.action()}
shouldShowRightIcon
+ brickRoadIndicator={item.error ? 'error' : null}
/>
))}
@@ -228,4 +241,9 @@ WorkspaceInitialPage.displayName = 'WorkspaceInitialPage';
export default compose(
withLocalize,
withFullPolicy,
+ withOnyx({
+ policyMemberList: {
+ key: ({policy}) => `${ONYXKEYS.COLLECTION.POLICY_MEMBER_LIST}${policy.id}`,
+ },
+ }),
)(WorkspaceInitialPage);
diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js
index 764da2999e21..8f10c91b1992 100644
--- a/src/pages/workspace/WorkspaceMembersPage.js
+++ b/src/pages/workspace/WorkspaceMembersPage.js
@@ -27,6 +27,7 @@ import CheckboxWithTooltip from '../../components/CheckboxWithTooltip';
import Hoverable from '../../components/Hoverable';
import withFullPolicy, {fullPolicyPropTypes, fullPolicyDefaultProps} from './withFullPolicy';
import CONST from '../../CONST';
+import OfflineWithFeedback from '../../components/OfflineWithFeedback';
const propTypes = {
/** The personal details of the person who is logged in */
@@ -186,6 +187,20 @@ class WorkspaceMembersPage extends React.Component {
}));
}
+ /**
+ * Dismisses the errors on one item
+ *
+ * @param {Object} item
+ */
+ dismissError(item) {
+ // TODO: login here also probably will need to change when connecting this to the real api
+ if (item.pendingAction === 'delete') {
+ Policy.clearDeleteMemberError(this.props.route.params.policyID, item.login);
+ } else {
+ Policy.clearAddMemberError(this.props.route.params.policyID, item.login);
+ }
+ }
+
/**
* Do not move this or make it an anonymous function it is a method
* so it will not be recreated each time we render an item
@@ -203,45 +218,47 @@ class WorkspaceMembersPage extends React.Component {
}) {
const canBeRemoved = this.props.policy.owner !== item.login && this.props.session.email !== item.login;
return (
- this.willTooltipShowForLogin(item.login, true)} onHoverOut={() => this.setState({showTooltipForLogin: ''})}>
- this.toggleUser(item.login)}
- activeOpacity={0.7}
- >
- this.dismissError(item)} pendingAction={item.pendingAction} errors={item.errors}>
+ this.willTooltipShowForLogin(item.login, true)} onHoverOut={() => this.setState({showTooltipForLogin: ''})}>
+ this.toggleUser(item.login)}
- toggleTooltip={this.state.showTooltipForLogin === item.login}
- text={this.props.translate('workspace.people.error.cannotRemove')}
- />
-
- this.toggleUser(item.login)}
- forceTextUnreadStyle
- isDisabled={!canBeRemoved}
- option={{
- text: Str.removeSMSDomain(item.displayName),
- alternateText: Str.removeSMSDomain(item.login),
- participantsList: [item],
- icons: [item.avatar],
- keyForList: item.login,
- }}
+ activeOpacity={0.7}
+ >
+ this.toggleUser(item.login)}
+ toggleTooltip={this.state.showTooltipForLogin === item.login}
+ text={this.props.translate('workspace.people.error.cannotRemove')}
/>
-
- {this.props.session.email === item.login && (
-
-
-
- {this.props.translate('common.admin')}
-
-
+
+ this.toggleUser(item.login)}
+ forceTextUnreadStyle
+ isDisabled={!canBeRemoved}
+ option={{
+ text: Str.removeSMSDomain(item.displayName),
+ alternateText: Str.removeSMSDomain(item.login),
+ participantsList: [item],
+ icons: [item.avatar],
+ keyForList: item.login,
+ }}
+ />
- )}
-
-
+ {this.props.session.email === item.login && (
+
+
+
+ {this.props.translate('common.admin')}
+
+
+
+ )}
+
+
+
);
}
@@ -252,6 +269,7 @@ class WorkspaceMembersPage extends React.Component {
.map(email => this.props.personalDetails[email])
.filter()
.sortBy(person => person.displayName.toLowerCase())
+ .map(person => ({...person})) // TODO: here we will add the pendingAction and errors prop
.value();
const policyID = lodashGet(this.props.route, 'params.policyID');
const policyName = lodashGet(this.props.policy, 'name');
diff --git a/src/styles/styles.js b/src/styles/styles.js
index 31efe3cb6be2..6c450b58f0a4 100644
--- a/src/styles/styles.js
+++ b/src/styles/styles.js
@@ -982,6 +982,7 @@ const styles = {
statusIndicator: {
borderColor: themeColors.sidebar,
+ backgroundColor: themeColors.buttonDangerBG,
borderRadius: 6,
borderWidth: 2,
position: 'absolute',
@@ -994,6 +995,7 @@ const styles = {
statusIndicatorLarge: {
borderColor: themeColors.componentBG,
+ backgroundColor: themeColors.buttonDangerBG,
borderRadius: 8,
borderWidth: 2,
position: 'absolute',
@@ -1008,8 +1010,18 @@ const styles = {
backgroundColor: themeColors.online,
},
- statusIndicatorOffline: {
- backgroundColor: themeColors.offline,
+ avatarWithIndicator: {
+ errorDot: {
+ borderColor: themeColors.sidebar,
+ borderRadius: 6,
+ borderWidth: 2,
+ position: 'absolute',
+ right: -1,
+ bottom: -1,
+ height: 12,
+ width: 12,
+ zIndex: 10,
+ },
},
floatingActionButton: {