Skip to content

Commit

Permalink
Merge pull request #34957 from software-mansion-labs/@szymczak/RoomMe…
Browse files Browse the repository at this point in the history
…mbersPage

[TS migration] Migrate RoomMembersPage to typescript
  • Loading branch information
lakchote committed Feb 16, 2024
2 parents 9843f66 + 317aff7 commit e3331db
Showing 1 changed file with 85 additions and 118 deletions.
203 changes: 85 additions & 118 deletions src/pages/RoomMembersPage.js → src/pages/RoomMembersPage.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import PropTypes from 'prop-types';
import type {StackScreenProps} from '@react-navigation/stack';
import React, {useCallback, useEffect, useMemo, useState} from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import type {OnyxEntry} from 'react-native-onyx';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
import Button from '@components/Button';
import ConfirmModal from '@components/ConfirmModal';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import {usePersonalDetails} from '@components/OnyxProvider';
import ScreenWrapper from '@components/ScreenWrapper';
import SelectionList from '@components/SelectionList';
import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions';
import type {User} from '@components/SelectionList/types';
import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails';
import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import compose from '@libs/compose';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import Log from '@libs/Log';
import Navigation from '@libs/Navigation/Navigation';
import type {RoomMembersNavigatorParamList} from '@libs/Navigation/types';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import * as PolicyUtils from '@libs/PolicyUtils';
Expand All @@ -27,51 +28,25 @@ import * as Report from '@userActions/Report';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
import type {Session} from '@src/types/onyx';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import type {WithReportOrNotFoundProps} from './home/report/withReportOrNotFound';
import withReportOrNotFound from './home/report/withReportOrNotFound';
import reportPropTypes from './reportPropTypes';

const propTypes = {
/** The report currently being looked at */
report: reportPropTypes.isRequired,

/** The policies which the user has access to and which the report could be tied to */
policies: PropTypes.shape({
/** ID of the policy */
id: PropTypes.string,
}),

/** URL Route params */
route: PropTypes.shape({
/** Params from the URL path */
params: PropTypes.shape({
/** reportID passed via route: /workspace/:reportID/members */
reportID: PropTypes.string,
}),
}).isRequired,

/** Session info for the currently logged in user. */
session: PropTypes.shape({
/** Currently logged in user accountID */
accountID: PropTypes.number,
}),

...withLocalizePropTypes,
...windowDimensionsPropTypes,
...withCurrentUserPersonalDetailsPropTypes,
type RoomMembersPageOnyxProps = {
session: OnyxEntry<Session>;
};

const defaultProps = {
session: {
accountID: 0,
},
report: {},
policies: {},
...withCurrentUserPersonalDetailsDefaultProps,
};
type RoomMembersPageProps = WithReportOrNotFoundProps &
WithCurrentUserPersonalDetailsProps &
RoomMembersPageOnyxProps &
StackScreenProps<RoomMembersNavigatorParamList, typeof SCREENS.ROOM_MEMBERS_ROOT>;

function RoomMembersPage(props) {
function RoomMembersPage({report, session, policies}: RoomMembersPageProps) {
const styles = useThemeStyles();
const [selectedMembers, setSelectedMembers] = useState([]);
const {formatPhoneNumber, translate} = useLocalize();
const [selectedMembers, setSelectedMembers] = useState<number[]>([]);
const [removeMembersConfirmModalVisible, setRemoveMembersConfirmModalVisible] = useState(false);
const [searchValue, setSearchValue] = useState('');
const [didLoadRoomMembers, setDidLoadRoomMembers] = useState(false);
Expand All @@ -81,9 +56,12 @@ function RoomMembersPage(props) {
* Get members for the current room
*/
const getRoomMembers = useCallback(() => {
Report.openRoomMembersPage(props.report.reportID);
if (!report) {
return;
}
Report.openRoomMembersPage(report.reportID);
setDidLoadRoomMembers(true);
}, [props.report.reportID]);
}, [report]);

useEffect(() => {
getRoomMembers();
Expand All @@ -94,73 +72,70 @@ function RoomMembersPage(props) {
* Open the modal to invite a user
*/
const inviteUser = () => {
if (!report) {
return;
}
setSearchValue('');
Navigation.navigate(ROUTES.ROOM_INVITE.getRoute(props.report.reportID));
Navigation.navigate(ROUTES.ROOM_INVITE.getRoute(report.reportID));
};

/**
* Remove selected users from the room
* Please see https://github.com/Expensify/App/blob/main/README.md#Security for more details
*/
const removeUsers = () => {
Report.removeFromRoom(props.report.reportID, selectedMembers);
if (report) {
Report.removeFromRoom(report.reportID, selectedMembers);
}
setSelectedMembers([]);
setRemoveMembersConfirmModalVisible(false);
};

/**
* Add user from the selectedMembers list
*
* @param {String} login
*/
const addUser = useCallback((accountID) => {
const addUser = useCallback((accountID: number) => {
setSelectedMembers((prevSelected) => [...prevSelected, accountID]);
}, []);

/**
* Remove user from the selectedEmployees list
*
* @param {String} login
*/
const removeUser = useCallback((accountID) => {
setSelectedMembers((prevSelected) => _.without(prevSelected, accountID));
const removeUser = useCallback((accountID: number) => {
setSelectedMembers((prevSelected) => prevSelected.filter((selected) => selected !== accountID));
}, []);

/**
* Toggle user from the selectedMembers list
*
* @param {String} accountID
* @param {String} pendingAction
*
*/
/** Toggle user from the selectedMembers list */
const toggleUser = useCallback(
(accountID, pendingAction) => {
if (pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) {
({accountID, pendingAction}: User) => {
if (pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || !accountID) {
return;
}

// Add or remove the user if the checkbox is enabled
if (_.contains(selectedMembers, Number(accountID))) {
removeUser(Number(accountID));
if (selectedMembers.includes(accountID)) {
removeUser(accountID);
} else {
addUser(Number(accountID));
addUser(accountID);
}
},
[selectedMembers, addUser, removeUser],
);

/**
* Add or remove all users passed from the selectedMembers list
* @param {Object} memberList
*/
const toggleAllUsers = (memberList) => {
const enabledAccounts = _.filter(memberList, (member) => !member.isDisabled);
const everyoneSelected = _.every(enabledAccounts, (member) => _.contains(selectedMembers, Number(member.keyForList)));
/** Add or remove all users passed from the selectedMembers list */
const toggleAllUsers = (memberList: User[]) => {
const enabledAccounts = memberList.filter((member) => !member.isDisabled);
const everyoneSelected = enabledAccounts.every((member) => {
if (!member.accountID) {
return false;
}
return selectedMembers.includes(member.accountID);
});

if (everyoneSelected) {
setSelectedMembers([]);
} else {
const everyAccountId = _.map(enabledAccounts, (member) => Number(member.keyForList));
const everyAccountId = enabledAccounts.map((member) => member.accountID).filter((accountID): accountID is number => !!accountID);
setSelectedMembers(everyAccountId);
}
};
Expand All @@ -172,10 +147,10 @@ function RoomMembersPage(props) {
setRemoveMembersConfirmModalVisible(true);
};

const getMemberOptions = () => {
let result = [];
const getMemberOptions = (): User[] => {
let result: User[] = [];

_.each(props.report.visibleChatMemberAccountIDs, (accountID) => {
report?.visibleChatMemberAccountIDs?.forEach((accountID) => {
const details = personalDetails[accountID];

if (!details) {
Expand Down Expand Up @@ -209,86 +184,93 @@ function RoomMembersPage(props) {

result.push({
keyForList: String(accountID),
accountID: Number(accountID),
isSelected: _.contains(selectedMembers, Number(accountID)),
isDisabled: accountID === props.session.accountID,
text: props.formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(details)),
alternateText: props.formatPhoneNumber(details.login),
accountID,
isSelected: selectedMembers.includes(accountID),
isDisabled: accountID === session?.accountID,
text: formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(details)),
alternateText: formatPhoneNumber(details.login),
icons: [
{
source: UserUtils.getAvatar(details.avatar, accountID),
name: details.login,
name: details.login ?? '',
type: CONST.ICON_TYPE_AVATAR,
id: Number(accountID),
},
],
});
});

result = _.sortBy(result, (value) => value.text.toLowerCase());
result = result.sort((value1, value2) => value1.text.localeCompare(value2.text.toLowerCase()));

return result;
};

const isPolicyMember = useMemo(() => PolicyUtils.isPolicyMember(props.report.policyID, props.policies), [props.report.policyID, props.policies]);
const isPolicyMember = useMemo(() => {
if (!report?.policyID || policies === null) {
return false;
}
return PolicyUtils.isPolicyMember(report.policyID, policies);
}, [report?.policyID, policies]);
const data = getMemberOptions();
const headerMessage = searchValue.trim() && !data.length ? props.translate('roomMembersPage.memberNotFound') : '';
const headerMessage = searchValue.trim() && !data.length ? translate('roomMembersPage.memberNotFound') : '';
return (
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
style={[styles.defaultModalContainer]}
testID={RoomMembersPage.displayName}
>
<FullPageNotFoundView
shouldShow={_.isEmpty(props.report) || !isPolicyMember}
subtitleKey={_.isEmpty(props.report) ? undefined : 'roomMembersPage.notAuthorized'}
onBackButtonPress={() => Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(props.report.reportID))}
shouldShow={isEmptyObject(report) || !isPolicyMember}
subtitleKey={isEmptyObject(report) ? undefined : 'roomMembersPage.notAuthorized'}
onBackButtonPress={() => {
Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report.reportID));
}}
>
<HeaderWithBackButton
title={props.translate('workspace.common.members')}
subtitle={ReportUtils.getReportName(props.report)}
title={translate('workspace.common.members')}
subtitle={ReportUtils.getReportName(report)}
onBackButtonPress={() => {
setSearchValue('');
Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(props.report.reportID));
Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report.reportID));
}}
/>
<ConfirmModal
danger
title={props.translate('workspace.people.removeMembersTitle')}
title={translate('workspace.people.removeMembersTitle')}
isVisible={removeMembersConfirmModalVisible}
onConfirm={removeUsers}
onCancel={() => setRemoveMembersConfirmModalVisible(false)}
prompt={props.translate('roomMembersPage.removeMembersPrompt')}
confirmText={props.translate('common.remove')}
cancelText={props.translate('common.cancel')}
prompt={translate('roomMembersPage.removeMembersPrompt')}
confirmText={translate('common.remove')}
cancelText={translate('common.cancel')}
/>
<View style={[styles.w100, styles.flex1]}>
<View style={[styles.w100, styles.flexRow, styles.pt3, styles.ph5]}>
<Button
medium
success
text={props.translate('common.invite')}
text={translate('common.invite')}
onPress={inviteUser}
/>
<Button
medium
danger
style={[styles.ml2]}
isDisabled={selectedMembers.length === 0}
text={props.translate('common.remove')}
text={translate('common.remove')}
onPress={askForConfirmationToRemove}
/>
</View>
<View style={[styles.w100, styles.mt4, styles.flex1]}>
<SelectionList
canSelectMultiple
sections={[{data, indexOffset: 0, isDisabled: false}]}
textInputLabel={props.translate('optionsSelector.findMember')}
textInputLabel={translate('optionsSelector.findMember')}
disableKeyboardShortcuts={removeMembersConfirmModalVisible}
textInputValue={searchValue}
onChangeText={setSearchValue}
headerMessage={headerMessage}
onSelectRow={(item) => toggleUser(item.keyForList)}
onSelectRow={(item) => toggleUser(item)}
onSelectAll={() => toggleAllUsers(data)}
showLoadingPlaceholder={!OptionsListUtils.isPersonalDetailsReady(personalDetails) || !didLoadRoomMembers}
showScrollIndicator
Expand All @@ -301,21 +283,6 @@ function RoomMembersPage(props) {
);
}

RoomMembersPage.propTypes = propTypes;
RoomMembersPage.defaultProps = defaultProps;
RoomMembersPage.displayName = 'RoomMembersPage';

export default compose(
withLocalize,
withWindowDimensions,
withReportOrNotFound(),
withOnyx({
session: {
key: ONYXKEYS.SESSION,
},
policies: {
key: ONYXKEYS.COLLECTION.POLICY,
},
}),
withCurrentUserPersonalDetails,
)(RoomMembersPage);
export default withReportOrNotFound()(withCurrentUserPersonalDetails(withOnyx<RoomMembersPageProps, RoomMembersPageOnyxProps>({session: {key: ONYXKEYS.SESSION}})(RoomMembersPage)));

0 comments on commit e3331db

Please sign in to comment.