Skip to content

Commit

Permalink
Merge pull request #14347 from Expensify/cmartins-fixMembersPage
Browse files Browse the repository at this point in the history
Fix Invite member checkbox selection behavior
  • Loading branch information
sketchydroide authored Jan 24, 2023
2 parents 49ded4f + b545c93 commit a10949d
Showing 1 changed file with 67 additions and 82 deletions.
149 changes: 67 additions & 82 deletions src/pages/workspace/WorkspaceMembersPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,14 @@ import ConfirmModal from '../../components/ConfirmModal';
import personalDetailsPropType from '../personalDetailsPropType';
import withWindowDimensions, {windowDimensionsPropTypes} from '../../components/withWindowDimensions';
import OptionRow from '../../components/OptionRow';
import CheckboxWithTooltip from '../../components/CheckboxWithTooltip';
import Hoverable from '../../components/Hoverable';
import withPolicy, {policyPropTypes, policyDefaultProps} from './withPolicy';
import CONST from '../../CONST';
import OfflineWithFeedback from '../../components/OfflineWithFeedback';
import {withNetwork} from '../../components/OnyxProvider';
import FullPageNotFoundView from '../../components/BlockingViews/FullPageNotFoundView';
import networkPropTypes from '../../components/networkPropTypes';
import * as Expensicons from '../../components/Icon/Expensicons';
import FormHelpMessage from '../../components/FormHelpMessage';

const propTypes = {
/** The personal details of the person who is logged in */
Expand Down Expand Up @@ -61,7 +60,7 @@ class WorkspaceMembersPage extends React.Component {
this.state = {
selectedEmployees: [],
isRemoveMembersConfirmModalVisible: false,
showTooltipForLogin: '',
errors: {},
};

this.renderItem = this.renderItem.bind(this);
Expand All @@ -77,6 +76,10 @@ class WorkspaceMembersPage extends React.Component {
}

componentDidUpdate(prevProps) {
if (prevProps.preferredLocale !== this.props.preferredLocale) {
this.validate();
}

const isReconnecting = prevProps.network.isOffline && !this.props.network.isOffline;
if (!isReconnecting) {
return;
Expand Down Expand Up @@ -110,6 +113,10 @@ class WorkspaceMembersPage extends React.Component {
* Remove selected users from the workspace
*/
removeUsers() {
if (!_.isEmpty(this.state.errors)) {
return;
}

// Remove the admin from the list
const membersToRemove = _.without(this.state.selectedEmployees, this.props.session.email);
Policy.removeMembers(membersToRemove, this.props.route.params.policyID);
Expand All @@ -123,6 +130,10 @@ class WorkspaceMembersPage extends React.Component {
* Show the modal to confirm removal of the selected members
*/
askForConfirmationToRemove() {
if (!_.isEmpty(this.state.errors)) {
return;
}

this.setState({isRemoveMembersConfirmModalVisible: true});
}

Expand All @@ -137,15 +148,14 @@ class WorkspaceMembersPage extends React.Component {
* Add or remove all users from the selectedEmployees list
*/
toggleAllUsers() {
this.setState({showTooltipForLogin: ''});
let policyMemberList = lodashGet(this.props, 'policyMemberList', {});
policyMemberList = _.filter(_.keys(policyMemberList), policyMember => policyMemberList[policyMember].pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE);
const removableMembers = _.without(policyMemberList, this.props.session.email, this.props.policy.owner);
this.setState(prevState => ({
selectedEmployees: removableMembers.length !== prevState.selectedEmployees.length
selectedEmployees: !_.every(removableMembers, member => _.contains(prevState.selectedEmployees, member))
? removableMembers
: [],
}));
}), () => this.validate());
}

/**
Expand All @@ -156,49 +166,16 @@ class WorkspaceMembersPage extends React.Component {
*
*/
toggleUser(login, pendingAction) {
if (this.willTooltipShowForLogin(login) || pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) {
if (pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) {
return;
}

// Add or remove the user if the checkbox is enabled and is clickable.
// Add or remove the user if the checkbox is enabled
if (_.contains(this.state.selectedEmployees, login)) {
this.removeUser(login);
} else {
this.addUser(login);
}

this.setState({showTooltipForLogin: ''});
}

/**
* Shows the tooltip for non removable members
*
* @param {String} login
* @param {Boolean} wasHovered
* @returns {Boolean} Return true if the tooltip was displayed so we can use the state of it in other functions.
*/
willTooltipShowForLogin(login, wasHovered = false) {
const isSmallOrMediumScreen = this.props.isSmallScreenWidth || this.props.isMediumScreenWidth;

// Small screens only show the tooltip on press, so ignore hovered event on those cases.
if (wasHovered && isSmallOrMediumScreen) {
return false;
}

const canBeRemoved = this.props.policy.owner !== login && this.props.session.email !== login;
if (!canBeRemoved) {
this.setState({
showTooltipForLogin: login,
}, () => {
// Immediately reset the login to deactivate the tooltip trigger, otherwise, the tooltip will not open again on further interactions on small screens.
if (!isSmallOrMediumScreen) {
return;
}
this.setState({showTooltipForLogin: ''});
});
}

return !canBeRemoved;
}

/**
Expand All @@ -209,7 +186,7 @@ class WorkspaceMembersPage extends React.Component {
addUser(login) {
this.setState(prevState => ({
selectedEmployees: [...prevState.selectedEmployees, login],
}));
}), () => this.validate());
}

/**
Expand All @@ -220,7 +197,7 @@ class WorkspaceMembersPage extends React.Component {
removeUser(login) {
this.setState(prevState => ({
selectedEmployees: _.without(prevState.selectedEmployees, login),
}));
}), () => this.validate());
}

/**
Expand All @@ -229,14 +206,26 @@ class WorkspaceMembersPage extends React.Component {
* @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);
}
}

validate() {
const errors = {};
_.each(this.state.selectedEmployees, (member) => {
if (member !== this.props.policy.owner && member !== this.props.session.email) {
return;
}

errors[member] = this.props.translate('workspace.people.error.cannotRemove');
});

this.setState({errors});
}

/**
* 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
Expand All @@ -252,48 +241,44 @@ class WorkspaceMembersPage extends React.Component {
renderItem({
item,
}) {
const canBeRemoved = this.props.policy.owner !== item.login && this.props.session.email !== item.login && item.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE;
return (
<OfflineWithFeedback errorRowStyles={[styles.peopleRowBorderBottom]} onClose={() => this.dismissError(item)} pendingAction={item.pendingAction} errors={item.errors}>
<Hoverable onHoverIn={() => this.willTooltipShowForLogin(item.login, true)} onHoverOut={() => this.setState({showTooltipForLogin: ''})}>
<TouchableOpacity
style={[styles.peopleRow, _.isEmpty(item.errors) && styles.peopleRowBorderBottom, !canBeRemoved && styles.cursorDisabled]}
<TouchableOpacity
style={[styles.peopleRow, (_.isEmpty(item.errors) || this.state.errors[item.login]) && styles.peopleRowBorderBottom]}
onPress={() => this.toggleUser(item.login, item.pendingAction)}
activeOpacity={0.7}
>
<Checkbox
style={[styles.peopleRowCell]}
isChecked={_.contains(this.state.selectedEmployees, item.login)}
onPress={() => this.toggleUser(item.login, item.pendingAction)}
activeOpacity={0.7}
>
<CheckboxWithTooltip
style={[styles.peopleRowCell]}
isChecked={_.contains(this.state.selectedEmployees, item.login)}
disabled={!canBeRemoved}
onPress={() => this.toggleUser(item.login, item.pendingAction)}
toggleTooltip={this.state.showTooltipForLogin === item.login}
text={this.props.translate('workspace.people.error.cannotRemove')}
/>
<View style={styles.flex1}>
<OptionRow
onSelectRow={() => this.toggleUser(item.login, item.pendingAction)}
boldStyle
option={{
text: Str.removeSMSDomain(item.displayName),
alternateText: Str.removeSMSDomain(item.login),
participantsList: [item],
icons: [item.avatar],
keyForList: item.login,
}}
/>
<View style={styles.flex1}>
<OptionRow
onSelectRow={() => this.toggleUser(item.login, item.pendingAction)}
boldStyle
isDisabled={!canBeRemoved}
option={{
text: Str.removeSMSDomain(item.displayName),
alternateText: Str.removeSMSDomain(item.login),
participantsList: [item],
icons: [item.avatar],
keyForList: item.login,
}}
/>
</View>
{(this.props.session.email === item.login || item.role === 'admin') && (
<View style={styles.peopleRowCell}>
<View style={[styles.badge, styles.peopleBadge]}>
<Text style={[styles.peopleBadgeText]}>
{this.props.translate('common.admin')}
</Text>
</View>
</View>
{(this.props.session.email === item.login || item.role === 'admin') && (
<View style={styles.peopleRowCell}>
<View style={[styles.badge, styles.peopleBadge]}>
<Text style={[styles.peopleBadgeText]}>
{this.props.translate('common.admin')}
</Text>
</View>
)}
</TouchableOpacity>
</Hoverable>
</View>
)}
</TouchableOpacity>
{!_.isEmpty(this.state.errors[item.login]) && (
<FormHelpMessage isError message={this.state.errors[item.login]} />
)}
</OfflineWithFeedback>
);
}
Expand Down Expand Up @@ -365,7 +350,7 @@ class WorkspaceMembersPage extends React.Component {
<View style={[styles.peopleRow, styles.ph5, styles.pb3]}>
<View style={[styles.peopleRowCell]}>
<Checkbox
isChecked={this.state.selectedEmployees.length === removableMembers.length && removableMembers.length !== 0}
isChecked={removableMembers.length !== 0 && _.every(removableMembers, member => _.contains(this.state.selectedEmployees, member))}
onPress={() => this.toggleAllUsers()}
/>
</View>
Expand Down

0 comments on commit a10949d

Please sign in to comment.