diff --git a/web/modals/threads/members/add-members-group.react.js b/web/modals/threads/members/add-members-group.react.js index 8fb56d08f..108cc44cd 100644 --- a/web/modals/threads/members/add-members-group.react.js +++ b/web/modals/threads/members/add-members-group.react.js @@ -1,44 +1,47 @@ // @flow import * as React from 'react'; import type { UserListItem } from 'lib/types/user-types'; import AddMembersItem from './add-members-item.react'; import css from './members-modal.css'; type AddMemberItemGroupProps = { - +header: string, + +header: ?string, +userInfos: $ReadOnlyArray, +onUserClick: (userID: string) => void, +usersAdded: $ReadOnlySet, }; function AddMembersItemGroup(props: AddMemberItemGroupProps): React.Node { const { userInfos, onUserClick, usersAdded, header } = props; const sortedUserInfos = React.useMemo(() => { return [...userInfos].sort((a, b) => a.username.localeCompare(b.username)); }, [userInfos]); const userInfosComponents = React.useMemo( () => sortedUserInfos.map(userInfo => ( )), [onUserClick, sortedUserInfos, usersAdded], ); + const headerComponent = header ? ( +
{header}:
+ ) : null; return ( <> -
{header}:
+ {headerComponent} {userInfosComponents} ); } export default AddMembersItemGroup; diff --git a/web/modals/threads/members/add-members-list.react.js b/web/modals/threads/members/add-members-list.react.js index 70966d9ad..ba1a90bc9 100644 --- a/web/modals/threads/members/add-members-list.react.js +++ b/web/modals/threads/members/add-members-list.react.js @@ -1,79 +1,85 @@ // @flow import _groupBy from 'lodash/fp/groupBy'; import _toPairs from 'lodash/fp/toPairs'; import * as React from 'react'; import type { UserListItem } from 'lib/types/user-types'; import AddMembersItemGroup from './add-members-group.react'; type Props = { +userListItems: $ReadOnlyArray, +pendingUsersToAdd: $ReadOnlySet, +switchUser: string => void, + +hasParentThread: boolean, }; function AddMembersList(props: Props): React.Node { - const { userListItems, pendingUsersToAdd, switchUser } = props; + const { + userListItems, + pendingUsersToAdd, + switchUser, + hasParentThread, + } = props; const usersAvailableToAdd = React.useMemo( () => userListItems.filter(user => !user.alertText), [userListItems], ); const groupedAvailableUsersList = React.useMemo( () => _groupBy(userInfo => userInfo.notice)(usersAvailableToAdd), [usersAvailableToAdd], ); - const membersInParentThread = React.useMemo( - () => - groupedAvailableUsersList['undefined'] - ? ['Users in parent channel', groupedAvailableUsersList['undefined']] - : undefined, - [groupedAvailableUsersList], - ); + const membersInParentThread = React.useMemo(() => { + if (!groupedAvailableUsersList['undefined']) { + return; + } + const label = hasParentThread ? 'Users in parent channel' : null; + return [label, groupedAvailableUsersList['undefined']]; + }, [groupedAvailableUsersList, hasParentThread]); const membersNotInParentThread = React.useMemo( () => _toPairs(groupedAvailableUsersList) .filter(group => group[0] !== 'undefined') .sort((a, b) => a[0].localeCompare(b[0])) .map(([header, users]) => [ header.charAt(0).toUpperCase() + header.substring(1), users, ]), [groupedAvailableUsersList], ); const usersUnavailableToAdd = React.useMemo(() => { const usersUnavailable = userListItems.filter(user => user.alertText); if (!usersUnavailable.length) { return null; } return ['Unavailable users', usersUnavailable]; }, [userListItems]); const sortedGroupedUsersList = React.useMemo( () => [ membersInParentThread, ...membersNotInParentThread, usersUnavailableToAdd, ].filter(Boolean), [membersInParentThread, membersNotInParentThread, usersUnavailableToAdd], ); return sortedGroupedUsersList.map(([header, userInfos]) => ( )); } export default AddMembersList; diff --git a/web/modals/threads/members/add-members-modal.react.js b/web/modals/threads/members/add-members-modal.react.js index a170ea06c..771a0a6be 100644 --- a/web/modals/threads/members/add-members-modal.react.js +++ b/web/modals/threads/members/add-members-modal.react.js @@ -1,195 +1,196 @@ // @flow import * as React from 'react'; import { changeThreadSettingsActionTypes, changeThreadSettings, } from 'lib/actions/thread-actions'; import { threadInfoSelector } from 'lib/selectors/thread-selectors'; import { userSearchIndexForPotentialMembers, userInfoSelectorForPotentialMembers, } from 'lib/selectors/user-selectors'; import { getPotentialMemberItems } from 'lib/shared/search-utils'; import { threadActualMembers } from 'lib/shared/thread-utils'; import { useDispatchActionPromise, useServerCall, } from 'lib/utils/action-utils'; import Button from '../../../components/button.react'; import Label from '../../../components/label.react'; import { useSelector } from '../../../redux/redux-utils'; import SearchModal from '../../search-modal.react'; import AddMembersList from './add-members-list.react'; import css from './members-modal.css'; type ContentProps = { +searchText: string, +threadID: string, +onClose: () => void, }; function AddMembersModalContent(props: ContentProps): React.Node { const { searchText, threadID, onClose } = props; const [pendingUsersToAdd, setPendingUsersToAdd] = React.useState< $ReadOnlySet, >(new Set()); const threadInfo = useSelector(state => threadInfoSelector(state)[threadID]); const { parentThreadID, community } = threadInfo; const parentThreadInfo = useSelector(state => parentThreadID ? threadInfoSelector(state)[parentThreadID] : null, ); const communityThreadInfo = useSelector(state => community ? threadInfoSelector(state)[community] : null, ); const otherUserInfos = useSelector(userInfoSelectorForPotentialMembers); const userSearchIndex = useSelector(userSearchIndexForPotentialMembers); const excludeUserIDs = React.useMemo( () => threadActualMembers(threadInfo.members).concat( Array.from(pendingUsersToAdd), ), [pendingUsersToAdd, threadInfo.members], ); const userSearchResults = React.useMemo( () => getPotentialMemberItems( searchText, otherUserInfos, userSearchIndex, excludeUserIDs, parentThreadInfo, communityThreadInfo, threadInfo.type, ), [ communityThreadInfo, excludeUserIDs, otherUserInfos, parentThreadInfo, searchText, threadInfo.type, userSearchIndex, ], ); const onSwitchUser = React.useCallback( userID => setPendingUsersToAdd(users => { const newUsers = new Set(users); if (newUsers.has(userID)) { newUsers.delete(userID); } else { newUsers.add(userID); } return newUsers; }), [], ); const dispatchActionPromise = useDispatchActionPromise(); const callChangeThreadSettings = useServerCall(changeThreadSettings); const addUsers = React.useCallback(() => { dispatchActionPromise( changeThreadSettingsActionTypes, callChangeThreadSettings({ threadID, changes: { newMemberIDs: Array.from(pendingUsersToAdd) }, }), ); onClose(); }, [ callChangeThreadSettings, dispatchActionPromise, onClose, pendingUsersToAdd, threadID, ]); const pendingUsersWithNames = React.useMemo( () => Array.from(pendingUsersToAdd) .map(userID => [userID, otherUserInfos[userID].username]) .sort((a, b) => a[1].localeCompare(b[1])), [otherUserInfos, pendingUsersToAdd], ); const labelItems = React.useMemo(() => { if (!pendingUsersWithNames.length) { return null; } return (
{pendingUsersWithNames.map(([userID, username]) => ( ))}
); }, [onSwitchUser, pendingUsersWithNames]); return (
{labelItems}
); } type Props = { +threadID: string, +onClose: () => void, }; function AddMembersModal(props: Props): React.Node { const { threadID, onClose } = props; const addMembersModalContent = React.useCallback( (searchText: string) => ( ), [onClose, threadID], ); return ( {addMembersModalContent} ); } export default AddMembersModal;