diff --git a/src/pages/RoomMembersPage.js b/src/pages/RoomMembersPage.tsx similarity index 58% rename from src/pages/RoomMembersPage.js rename to src/pages/RoomMembersPage.tsx index a86d1c3b9b56..d5974fbbbf5e 100644 --- a/src/pages/RoomMembersPage.js +++ b/src/pages/RoomMembersPage.tsx @@ -1,8 +1,8 @@ -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'; @@ -10,14 +10,15 @@ 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'; @@ -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; }; -const defaultProps = { - session: { - accountID: 0, - }, - report: {}, - policies: {}, - ...withCurrentUserPersonalDetailsDefaultProps, -}; +type RoomMembersPageProps = WithReportOrNotFoundProps & + WithCurrentUserPersonalDetailsProps & + RoomMembersPageOnyxProps & + StackScreenProps; -function RoomMembersPage(props) { +function RoomMembersPage({report, session, policies}: RoomMembersPageProps) { const styles = useThemeStyles(); - const [selectedMembers, setSelectedMembers] = useState([]); + const {formatPhoneNumber, translate} = useLocalize(); + const [selectedMembers, setSelectedMembers] = useState([]); const [removeMembersConfirmModalVisible, setRemoveMembersConfirmModalVisible] = useState(false); const [searchValue, setSearchValue] = useState(''); const [didLoadRoomMembers, setDidLoadRoomMembers] = useState(false); @@ -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(); @@ -94,8 +72,11 @@ 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)); }; /** @@ -103,64 +84,58 @@ function RoomMembersPage(props) { * 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); } }; @@ -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) { @@ -209,15 +184,15 @@ 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), }, @@ -225,14 +200,19 @@ function RoomMembersPage(props) { }); }); - 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 ( 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)); + }} > { setSearchValue(''); - Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(props.report.reportID)); + Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report.reportID)); }} /> 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')} />