-
Notifications
You must be signed in to change notification settings - Fork 3.9k
[TS migration] Migrate RoomMembersPage to typescript #34957
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
58bf6c4
d6381c1
c8fbd90
c524aba
c0d2681
808919b
dc472c5
818c68e
4feaa63
c906794
d7c8fb4
130b05b
83c91a8
5d6cec8
5a6196b
317aff7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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'; | ||
|
|
@@ -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); | ||
|
|
@@ -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,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 | ||
|
SzymczakJ marked this conversation as resolved.
|
||
| * | ||
| * @param {String} login | ||
| */ | ||
| const addUser = useCallback((accountID) => { | ||
| const addUser = useCallback((accountID: number) => { | ||
| setSelectedMembers((prevSelected) => [...prevSelected, accountID]); | ||
| }, []); | ||
|
|
||
| /** | ||
| * Remove user from the selectedEmployees list | ||
|
SzymczakJ marked this conversation as resolved.
|
||
| * | ||
| * @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); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since you updated the logic (use
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yep, keyForList is actually string created by String(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,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} | ||
|
SzymczakJ marked this conversation as resolved.
|
||
| 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 | ||
|
|
@@ -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))); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we setting this true even if
reportis not set?