diff --git a/web/modals/threads/members/members-modal.css b/web/modals/threads/members/members-modal.css index 13a668ab8..394566adc 100644 --- a/web/modals/threads/members/members-modal.css +++ b/web/modals/threads/members/members-modal.css @@ -1,114 +1,127 @@ div.modalContentContainer { width: 383px; height: 617px; + overflow: hidden; + display: flex; + flex-direction: column; + row-gap: 16px; +} + +div.membersListTabs { + flex: 1; + overflow: hidden; +} + +div.addNewMembers button { + width: 100%; } div.membersList { overflow: auto; padding: 8px 0; color: var(--members-modal-member-text); } div.noScroll { overflow: hidden; } div.memberContainer { display: flex; flex-direction: row; justify-content: space-between; padding: 8px 16px; } div.memberContainer:hover { color: var(--members-modal-member-text-hover); } div.memberContainerWithMenuOpen { color: var(--members-modal-member-text-hover); } div.memberInfo { font-size: var(--l-font-18); display: flex; align-items: center; gap: 10px; } div.memberAction { position: relative; } h5.memberletterHeader { margin: 16px; color: var(--members-modal-member-text); font-size: var(--s-font-14); } div.noUsers { padding-top: 16px; text-align: center; } div.addMembersContent { display: flex; flex-direction: column; overflow: auto; color: var(--fg); row-gap: 16px; width: 383px; height: 617px; } div.addMembersListContainer { overflow: auto; flex: 1; } div.addMembersFooter { display: flex; justify-content: end; column-gap: 16px; } div.addMemberItemsGroupHeader { font-size: var(--s-font-14); color: var(--add-members-group-header-color); margin: 16px; } button.addMemberItem { display: flex; flex-direction: row; justify-content: space-between; align-items: center; color: var(--add-members-item-color); font-size: var(--l-font-18); background-color: transparent; border: none; width: 100%; } button.addMemberItem:hover { color: var(--add-members-item-color-hover); } button.addMemberItem:disabled { color: var(--add-members-item-disabled-color); cursor: not-allowed; } button.addMemberItem:hover:disabled { color: var(--add-members-item-disabled-color-hover); } button.addMemberItem .label { padding: 8px 16px; } button.addMemberItem .danger { color: var(--add-members-remove-pending-color); } button.addMemberItem:hover .danger { color: var(--add-members-remove-pending-color-hover); } diff --git a/web/modals/threads/members/members-modal.react.js b/web/modals/threads/members/members-modal.react.js index 0be54a27f..c5aec0b1c 100644 --- a/web/modals/threads/members/members-modal.react.js +++ b/web/modals/threads/members/members-modal.react.js @@ -1,106 +1,141 @@ // @flow import * as React from 'react'; import { threadInfoSelector } from 'lib/selectors/thread-selectors'; import { userStoreSearchIndex } from 'lib/selectors/user-selectors'; -import { memberHasAdminPowers, memberIsAdmin } from 'lib/shared/thread-utils'; -import { type RelativeMemberInfo } from 'lib/types/thread-types'; +import { + memberHasAdminPowers, + memberIsAdmin, + threadHasPermission, +} from 'lib/shared/thread-utils'; +import { + type RelativeMemberInfo, + threadPermissions, +} from 'lib/types/thread-types'; +import Button from '../../../components/button.react'; import Tabs from '../../../components/tabs.react'; import { useSelector } from '../../../redux/redux-utils'; +import { useModalContext } from '../../modal-provider.react'; import SearchModal from '../../search-modal.react'; +import AddMembersModal from './add-members-modal.react'; import ThreadMembersList from './members-list.react'; import css from './members-modal.css'; type ContentProps = { +searchText: string, +threadID: string, }; function ThreadMembersModalContent(props: ContentProps): React.Node { const { threadID, searchText } = props; const [tab, setTab] = React.useState<'All Members' | 'Admins'>('All Members'); const threadInfo = useSelector(state => threadInfoSelector(state)[threadID]); const { members: threadMembersNotFiltered } = threadInfo; const userSearchIndex = useSelector(userStoreSearchIndex); const userIDs = React.useMemo( () => userSearchIndex.getSearchResults(searchText), [searchText, userSearchIndex], ); const allMembers = React.useMemo( () => threadMembersNotFiltered.filter( (member: RelativeMemberInfo) => searchText.length === 0 || userIDs.includes(member.id), ), [searchText.length, threadMembersNotFiltered, userIDs], ); const adminMembers = React.useMemo( () => allMembers.filter( (member: RelativeMemberInfo) => memberIsAdmin(member, threadInfo) || memberHasAdminPowers(member), ), [allMembers, threadInfo], ); const allUsersTab = React.useMemo( () => ( ), [allMembers, threadInfo], ); const allAdminsTab = React.useMemo( () => ( ), [adminMembers, threadInfo], ); + const { pushModal, popModal } = useModalContext(); + + const onClickAddMembers = React.useCallback(() => { + pushModal(); + }, [popModal, pushModal, threadID]); + + const canAddMembers = threadHasPermission( + threadInfo, + threadPermissions.ADD_MEMBERS, + ); + + const addMembersButton = React.useMemo(() => { + if (!canAddMembers) { + return null; + } + return ( +
+ +
+ ); + }, [canAddMembers, onClickAddMembers]); + return (
- - {allUsersTab} - {allAdminsTab} - +
+ + {allUsersTab} + {allAdminsTab} + +
+ {addMembersButton}
); } type Props = { +threadID: string, +onClose: () => void, }; function ThreadMembersModal(props: Props): React.Node { const { onClose, threadID } = props; const renderModalContent = React.useCallback( (searchText: string) => ( ), [threadID], ); return ( {renderModalContent} ); } export default ThreadMembersModal;