diff --git a/web/modals/threads/create/compose-subchannel-modal.react.js b/web/modals/threads/create/compose-subchannel-modal.react.js --- a/web/modals/threads/create/compose-subchannel-modal.react.js +++ b/web/modals/threads/create/compose-subchannel-modal.react.js @@ -22,6 +22,10 @@ import { updateNavInfoActionType } from '../../../redux/action-types.js'; import { useSelector } from '../../../redux/redux-utils.js'; import { nonThreadCalendarQuery } from '../../../selectors/nav-selectors.js'; +import { + useAddUsersListContext, + AddUsersListProvider, +} from '../../../settings/relationship/add-users-list-provider.react.js'; import Modal from '../../modal.react.js'; type Props = { @@ -64,16 +68,14 @@ const { parentThreadInfo, onClose } = props; const { uiName: parentThreadName } = useResolvedThreadInfo(parentThreadInfo); + const { pendingUsersToAdd } = useAddUsersListContext(); + const [activeStep, setActiveStep] = React.useState('settings'); const [channelName, setChannelName] = React.useState(''); const [visibilityType, setVisibilityType] = React.useState('open'); const [announcement, setAnnouncement] = React.useState(false); - const [selectedUsers, setSelectedUsers] = React.useState< - $ReadOnlySet, - >(new Set()); - const [searchUserText, setSearchUserText] = React.useState(''); const loadingState = useSelector(createSubchannelLoadingStatusSelector); @@ -94,7 +96,7 @@ name: channelName, type: threadType, parentThreadID: parentThreadInfo.id, - initialMemberIDs: Array.from(selectedUsers), + initialMemberIDs: Array.from(pendingUsersToAdd.keys()), calendarQuery: query, color: parentThreadInfo.color, }); @@ -105,13 +107,14 @@ return null; } }, [ - parentThreadInfo, - selectedUsers, visibilityType, announcement, - callNewThread, calendarQuery, + callNewThread, channelName, + parentThreadInfo.id, + parentThreadInfo.color, + pendingUsersToAdd, ]); const dispatchCreateSubchannel = React.useCallback(async () => { @@ -157,18 +160,6 @@ [announcement], ); - const toggleUserSelection = React.useCallback((userID: string) => { - setSelectedUsers((users: $ReadOnlySet) => { - const newUsers = new Set(users); - if (newUsers.has(userID)) { - newUsers.delete(userID); - } else { - newUsers.add(userID); - } - return newUsers; - }); - }, []); - const subchannelSettings = React.useMemo( () => ( ( - - ), - [ - selectedUsers, - toggleUserSelection, - parentThreadInfo, - searchUserText, - setSearchUserText, - ], + () => , + [parentThreadInfo], ); const modalName = @@ -273,4 +255,17 @@ ); } -export default ComposeSubchannelModal; +function ComposeSubchannelModalWrapper(props: Props): React.Node { + const composeSubchannelModalWrapper = React.useMemo( + () => ( + + + + ), + [props], + ); + + return composeSubchannelModalWrapper; +} + +export default ComposeSubchannelModalWrapper; diff --git a/web/modals/threads/create/steps/subchannel-members-list.react.js b/web/modals/threads/create/steps/subchannel-members-list.react.js deleted file mode 100644 --- a/web/modals/threads/create/steps/subchannel-members-list.react.js +++ /dev/null @@ -1,108 +0,0 @@ -// @flow - -import * as React from 'react'; - -import { useENSNames } from 'lib/hooks/ens-cache.js'; -import { stringForUser } from 'lib/shared/user-utils.js'; -import type { - RelativeMemberInfo, - ThreadInfo, -} from 'lib/types/minimally-encoded-thread-permissions-types.js'; -import type { UserListItem } from 'lib/types/user-types.js'; - -import { useSelector } from '../../../../redux/redux-utils.js'; -import AddMembersList from '../../../components/add-members-list.react.js'; - -type Props = { - +searchText: string, - +searchResult: $ReadOnlySet, - +communityThreadInfo: ThreadInfo, - +parentThreadInfo: ThreadInfo, - +selectedUsers: $ReadOnlySet, - +toggleUserSelection: (userID: string) => void, -}; - -function SubchannelMembersList(props: Props): React.Node { - const { - searchText, - searchResult, - communityThreadInfo, - parentThreadInfo, - selectedUsers, - toggleUserSelection, - } = props; - - const { name: communityName } = communityThreadInfo; - - const currentUserId = useSelector(state => state.currentUserInfo?.id); - - const parentMembersSet = React.useMemo( - () => new Set(parentThreadInfo.members.map(user => user.id)), - [parentThreadInfo], - ); - - const filterOutParentMembersWithENSNames = React.useCallback( - (members: $ReadOnlyArray) => - members - .filter( - user => - user.id !== currentUserId && - (searchResult.has(user.id) || searchText.length === 0), - ) - .map(user => ({ id: user.id, username: stringForUser(user) })), - [currentUserId, searchResult, searchText.length], - ); - - const parentMemberListWithoutENSNames = React.useMemo( - () => filterOutParentMembersWithENSNames(parentThreadInfo.members), - [filterOutParentMembersWithENSNames, parentThreadInfo.members], - ); - - const parentMemberList = useENSNames( - parentMemberListWithoutENSNames, - ); - - const filterOutOtherMembersWithENSNames = React.useCallback( - (members: $ReadOnlyArray) => - members - .filter( - user => - !parentMembersSet.has(user.id) && - user.id !== currentUserId && - (searchResult.has(user.id) || searchText.length === 0), - ) - .map(user => ({ id: user.id, username: stringForUser(user) })), - [currentUserId, parentMembersSet, searchResult, searchText.length], - ); - - const otherMemberListWithoutENSNames = React.useMemo( - () => filterOutOtherMembersWithENSNames(communityThreadInfo.members), - [communityThreadInfo.members, filterOutOtherMembersWithENSNames], - ); - - const otherMemberList = useENSNames( - otherMemberListWithoutENSNames, - ); - - const sortedGroupedUserList = React.useMemo( - () => - [ - { header: 'Users in parent channel', userInfos: parentMemberList }, - { - header: `All users in ${communityName ?? 'community'}`, - userInfos: otherMemberList, - }, - ].filter(item => item.userInfos.length), - [parentMemberList, otherMemberList, communityName], - ); - - return ( - - ); -} - -export default SubchannelMembersList; diff --git a/web/modals/threads/create/steps/subchannel-members.css b/web/modals/threads/create/steps/subchannel-members.css --- a/web/modals/threads/create/steps/subchannel-members.css +++ b/web/modals/threads/create/steps/subchannel-members.css @@ -1,10 +1,5 @@ -.members { - overflow-y: auto; -} - .searchBar { - background-color: var(--modal-bg); position: sticky; - padding: 2.5px 0; top: 0; + z-index: 1; } diff --git a/web/modals/threads/create/steps/subchannel-members.react.js b/web/modals/threads/create/steps/subchannel-members.react.js --- a/web/modals/threads/create/steps/subchannel-members.react.js +++ b/web/modals/threads/create/steps/subchannel-members.react.js @@ -2,60 +2,42 @@ import * as React from 'react'; -import { useUserSearchIndex } from 'lib/selectors/nav-selectors.js'; -import { useAncestorThreads } from 'lib/shared/ancestor-threads.js'; import type { ThreadInfo } from 'lib/types/minimally-encoded-thread-permissions-types.js'; -import MembersList from './subchannel-members-list.react.js'; import css from './subchannel-members.css'; import Search from '../../../../components/search.react.js'; +import AddUsersList from '../../../../settings/relationship/add-users-list.react.js'; +import { useSubchannelAddMembersListUserInfos } from '../../../../settings/relationship/add-users-utils.js'; type SubchannelMembersProps = { +parentThreadInfo: ThreadInfo, - +selectedUsers: $ReadOnlySet, - +searchText: string, - +setSearchText: string => void, - +toggleUserSelection: (userID: string) => void, }; function SubchannelMembers(props: SubchannelMembersProps): React.Node { - const { - toggleUserSelection, - searchText, - setSearchText, - parentThreadInfo, - selectedUsers, - } = props; - - const ancestorThreads = useAncestorThreads(parentThreadInfo); - - const communityThread = ancestorThreads[0] ?? parentThreadInfo; - - const userSearchIndex = useUserSearchIndex(communityThread.members); - const searchResult = React.useMemo( - () => new Set(userSearchIndex.getSearchResults(searchText)), - [userSearchIndex, searchText], - ); + const { parentThreadInfo } = props; + + const [searchUserText, setSearchUserText] = React.useState(''); + + const { userInfos, sortedUsersWithENSNames } = + useSubchannelAddMembersListUserInfos({ + parentThreadID: parentThreadInfo.id, + searchText: searchUserText, + }); return ( <>
-
- -
+ 0} + userInfos={userInfos} + sortedUsersWithENSNames={sortedUsersWithENSNames} + /> ); } diff --git a/web/settings/relationship/add-users-utils.js b/web/settings/relationship/add-users-utils.js --- a/web/settings/relationship/add-users-utils.js +++ b/web/settings/relationship/add-users-utils.js @@ -6,11 +6,13 @@ import { useUserSearchIndex } from 'lib/selectors/nav-selectors.js'; import { threadInfoSelector } from 'lib/selectors/thread-selectors.js'; import { userInfoSelectorForPotentialMembers } from 'lib/selectors/user-selectors.js'; +import { useAncestorThreads } from 'lib/shared/ancestor-threads.js'; import { useSearchUsers, usePotentialMemberItems, } from 'lib/shared/search-utils.js'; import { threadActualMembers } from 'lib/shared/thread-utils.js'; +import type { RelativeMemberInfo } from 'lib/types/minimally-encoded-thread-permissions-types.js'; import type { UserRelationshipStatus } from 'lib/types/relationship-types.js'; import type { GlobalAccountUserInfo, @@ -182,4 +184,78 @@ return result; } -export { useUserRelationshipUserInfos, useAddMembersListUserInfos }; +type UseSubchannelAddMembersListUserInfosParams = { + +parentThreadID: string, + +searchText: string, +}; + +function useSubchannelAddMembersListUserInfos( + params: UseSubchannelAddMembersListUserInfosParams, +): { + +userInfos: { + [string]: RelativeMemberInfo, + }, + +sortedUsersWithENSNames: $ReadOnlyArray, +} { + const { parentThreadID, searchText } = params; + + const { previouslySelectedUsers } = useAddUsersListContext(); + + const parentThreadInfo = useSelector( + state => threadInfoSelector(state)[parentThreadID], + ); + + const currentUserID = useSelector(state => state.currentUserInfo?.id); + + const ancestorThreads = useAncestorThreads(parentThreadInfo); + + const communityThreadInfo = ancestorThreads[0] ?? parentThreadInfo; + + const userInfos = React.useMemo(() => { + const infos: { [string]: RelativeMemberInfo } = {}; + + for (const member of communityThreadInfo.members) { + infos[member.id] = member; + } + + return infos; + }, [communityThreadInfo.members]); + + const userSearchIndex = useUserSearchIndex(communityThreadInfo.members); + + const searchResult = React.useMemo( + () => new Set(userSearchIndex.getSearchResults(searchText)), + [userSearchIndex, searchText], + ); + + const filterOutOtherMembersWithENSNames = React.useCallback( + (members: $ReadOnlyArray) => + members.filter( + user => + !previouslySelectedUsers.has(user.id) && + user.id !== currentUserID && + (searchResult.has(user.id) || searchText.length === 0), + ), + [currentUserID, previouslySelectedUsers, searchResult, searchText.length], + ); + + const otherMemberListWithoutENSNames = React.useMemo( + () => filterOutOtherMembersWithENSNames(communityThreadInfo.members), + [communityThreadInfo.members, filterOutOtherMembersWithENSNames], + ); + + const sortedUsersWithENSNames = useSortedENSResolvedUsers( + otherMemberListWithoutENSNames, + ); + + return { + userInfos, + sortedUsersWithENSNames, + }; +} + +export { + useUserRelationshipUserInfos, + useAddMembersListUserInfos, + useSubchannelAddMembersListUserInfos, +};