Skip to content

Commit

Permalink
Merge pull request #10065 from Expensify/ionatan_brickroad_policy
Browse files Browse the repository at this point in the history
Add indicator to avatar
  • Loading branch information
iwiznia authored Aug 1, 2022
2 parents 631994f + 1803d86 commit b71fd5d
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 154 deletions.
142 changes: 41 additions & 101 deletions src/components/AvatarWithIndicator.js
Original file line number Diff line number Diff line change
@@ -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 (
<View
style={[this.props.size === 'large' ? styles.avatarLarge : styles.sidebarAvatar]}
>
<Tooltip text={this.props.tooltipText}>
<Avatar
imageStyles={[this.props.size === 'large' ? styles.avatarLarge : null]}
source={this.props.source}
size={this.props.size}
/>
</Tooltip>
<Tooltip text={this.userStatus()} absolute>
<Animated.View style={StyleSheet.flatten(indicatorStyles)}>
{this.props.isSyncing && (
<Icon
src={Expensicons.Sync}
fill={themeColors.textReversed}
width={6}
height={6}
/>
)}
</Animated.View>
</Tooltip>
</View>
);
}
}
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 (
<View style={[isLarge ? styles.avatarLarge : styles.sidebarAvatar]}>
<Tooltip text={props.tooltipText}>
<Avatar
imageStyles={[isLarge ? styles.avatarLarge : null]}
source={props.source}
size={props.size}
/>
{hasPolicyMemberError && (
<View style={StyleSheet.flatten(indicatorStyles)} />
)}
</Tooltip>
</View>
);
};

AvatarWithIndicator.defaultProps = defaultProps;
AvatarWithIndicator.propTypes = propTypes;
export default withLocalize(AvatarWithIndicator);
AvatarWithIndicator.displayName = 'AvatarWithIndicator';

export default withOnyx({
policiesMemberList: {
key: ONYXKEYS.COLLECTION.POLICY_MEMBER_LIST,
},
})(AvatarWithIndicator);
1 change: 1 addition & 0 deletions src/components/OfflineWithFeedback.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ const OfflineWithFeedback = (props) => {

OfflineWithFeedback.propTypes = propTypes;
OfflineWithFeedback.defaultProps = defaultProps;
OfflineWithFeedback.displayName = 'OfflineWithFeedback';

export default compose(
withLocalize,
Expand Down
31 changes: 30 additions & 1 deletion src/libs/actions/Policy.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -567,5 +594,7 @@ export {
setCustomUnitRate,
updateLastAccessedWorkspace,
subscribeToPolicyEvents,
clearDeleteMemberError,
clearAddMemberError,
hasPolicyMemberError,
};
3 changes: 3 additions & 0 deletions src/pages/policyMemberPropType.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ export default PropTypes.shape({
* {<timestamp>: 'error message', <timestamp2>: 'error message 2'}
*/
errors: PropTypes.objectOf(PropTypes.string),

/** Is this action pending? */
pendingAction: PropTypes.string,
});
22 changes: 9 additions & 13 deletions src/pages/settings/InitialSettingsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand All @@ -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 */
Expand Down Expand Up @@ -156,12 +152,13 @@ const InitialSettingsPage = (props) => {
<View style={styles.w100}>
<View style={styles.pageWrapper}>
<Pressable style={[styles.mb3]} onPress={openProfileSettings}>
<AvatarWithIndicator
size={CONST.AVATAR_SIZE.LARGE}
source={props.currentUserPersonalDetails.avatar}
isActive={props.network.isOffline === false}
tooltipText={props.currentUserPersonalDetails.displayName}
/>
<Tooltip text={props.currentUserPersonalDetails.displayName}>
<Avatar
imageStyles={[styles.avatarLarge]}
source={props.currentUserPersonalDetails.avatar}
size={CONST.AVATAR_SIZE.LARGE}
/>
</Tooltip>
</Pressable>

<Pressable style={[styles.mt1, styles.mw100]} onPress={openProfileSettings}>
Expand Down Expand Up @@ -211,7 +208,6 @@ InitialSettingsPage.displayName = 'InitialSettingsPage';

export default compose(
withLocalize,
withNetwork(),
withCurrentUserPersonalDetails,
withOnyx({
session: {
Expand Down
20 changes: 19 additions & 1 deletion src/pages/workspace/WorkspaceInitialPage.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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) {
Expand Down Expand Up @@ -70,6 +80,7 @@ class WorkspaceInitialPage extends React.Component {
return <FullScreenLoadingIndicator />;
}

const hasMembersError = PolicyActions.hasPolicyMemberError(this.props.policyMemberList);
const menuItems = [
{
translationKey: 'workspace.common.settings',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -202,6 +214,7 @@ class WorkspaceInitialPage extends React.Component {
iconRight={item.iconRight}
onPress={() => item.action()}
shouldShowRightIcon
brickRoadIndicator={item.error ? 'error' : null}
/>
))}
</View>
Expand All @@ -228,4 +241,9 @@ WorkspaceInitialPage.displayName = 'WorkspaceInitialPage';
export default compose(
withLocalize,
withFullPolicy,
withOnyx({
policyMemberList: {
key: ({policy}) => `${ONYXKEYS.COLLECTION.POLICY_MEMBER_LIST}${policy.id}`,
},
}),
)(WorkspaceInitialPage);
Loading

0 comments on commit b71fd5d

Please sign in to comment.