diff --git a/web/modals/threads/settings/thread-settings-modal.css b/web/modals/threads/settings/thread-settings-modal.css index ea4c8fa59..73df5420a 100644 --- a/web/modals/threads/settings/thread-settings-modal.css +++ b/web/modals/threads/settings/thread-settings-modal.css @@ -1,15 +1,19 @@ div.modal_body { display: flex; flex-direction: column; + width: 383px; + height: 539px; + overflow: hidden; } div.tab_body { padding: 20px; + overflow: auto; } div.modal_form_error { display: flex; justify-content: center; padding-top: 8px; font-size: 16px; color: red; font-style: italic; } diff --git a/web/modals/threads/settings/thread-settings-modal.react.js b/web/modals/threads/settings/thread-settings-modal.react.js index 6cdff75ee..8274f1c9a 100644 --- a/web/modals/threads/settings/thread-settings-modal.react.js +++ b/web/modals/threads/settings/thread-settings-modal.react.js @@ -1,187 +1,227 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import { deleteThreadActionTypes, changeThreadSettingsActionTypes, } from 'lib/actions/thread-actions'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors'; import { threadInfoSelector } from 'lib/selectors/thread-selectors'; -import { threadHasPermission, robotextName } from 'lib/shared/thread-utils'; +import { getAvailableRelationshipButtons } from 'lib/shared/relationship-utils'; +import { + threadHasPermission, + robotextName, + getSingleOtherUser, +} from 'lib/shared/thread-utils'; import { type ThreadInfo, threadTypes, threadPermissions, type ThreadChanges, } from 'lib/types/thread-types'; import Tabs from '../../../components/tabs.react'; import { useSelector } from '../../../redux/redux-utils'; import { useModalContext } from '../../modal-provider.react'; import Modal from '../../modal.react'; import ThreadSettingsDeleteTab from './thread-settings-delete-tab.react'; import ThreadSettingsGeneralTab from './thread-settings-general-tab.react'; import css from './thread-settings-modal.css'; import ThreadSettingsPrivacyTab from './thread-settings-privacy-tab.react'; +import ThreadSettingsRelationshipTab from './thread-settings-relationship-tab.react'; -type TabType = 'general' | 'privacy' | 'delete'; +type TabType = 'general' | 'privacy' | 'delete' | 'relationship'; type BaseProps = { +threadID: string, }; const deleteThreadLoadingStatusSelector = createLoadingStatusSelector( deleteThreadActionTypes, ); const changeThreadSettingsLoadingStatusSelector = createLoadingStatusSelector( changeThreadSettingsActionTypes, ); const ConnectedThreadSettingsModal: React.ComponentType = React.memo( function ConnectedThreadSettingsModal(props) { const changeInProgress = useSelector( state => deleteThreadLoadingStatusSelector(state) === 'loading' || changeThreadSettingsLoadingStatusSelector(state) === 'loading', ); const viewerID = useSelector( state => state.currentUserInfo && state.currentUserInfo.id, ); const userInfos = useSelector(state => state.userStore.userInfos); const threadInfo: ?ThreadInfo = useSelector( state => threadInfoSelector(state)[props.threadID], ); const modalContext = useModalContext(); const [errorMessage, setErrorMessage] = React.useState(''); const [currentTabType, setCurrentTabType] = React.useState( 'general', ); const [queuedChanges, setQueuedChanges] = React.useState( Object.freeze({}), ); const namePlaceholder: string = React.useMemo(() => { invariant(threadInfo, 'threadInfo should exist in namePlaceholder'); return robotextName(threadInfo, viewerID, userInfos); }, [threadInfo, userInfos, viewerID]); + const otherMemberID = React.useMemo(() => { + if (!threadInfo) { + return null; + } + return getSingleOtherUser(threadInfo, viewerID); + }, [threadInfo, viewerID]); + + const otherUserInfo = otherMemberID ? userInfos[otherMemberID] : null; + + const availableRelationshipActions = React.useMemo(() => { + if (!otherUserInfo) { + return []; + } + return getAvailableRelationshipButtons(otherUserInfo); + }, [otherUserInfo]); + const hasPermissionForTab = React.useCallback( (thread: ThreadInfo, tab: TabType) => { if (tab === 'general') { return ( threadHasPermission(thread, threadPermissions.EDIT_THREAD_NAME) || threadHasPermission(thread, threadPermissions.EDIT_THREAD_COLOR) || threadHasPermission( thread, threadPermissions.EDIT_THREAD_DESCRIPTION, ) ); } else if (tab === 'privacy') { return threadHasPermission( thread, threadPermissions.EDIT_PERMISSIONS, ); } else if (tab === 'delete') { return threadHasPermission(thread, threadPermissions.DELETE_THREAD); + } else if (tab === 'relationship') { + return true; } invariant(false, `invalid tab: ${tab}`); }, [], ); React.useEffect(() => { if ( threadInfo && currentTabType !== 'general' && !hasPermissionForTab(threadInfo, currentTabType) ) { setCurrentTabType('general'); } }, [currentTabType, hasPermissionForTab, threadInfo]); if (!threadInfo) { return (

You no longer have permission to view this chat

); } const tabs = [
{errorMessage}
, ]; // This UI needs to be updated to handle sidebars but we haven't gotten // there yet. We'll probably end up ripping it out anyways, so for now we // are just hiding the privacy tab for any thread that was created as a // sidebar const canSeePrivacyTab = (queuedChanges['parentThreadID'] ?? threadInfo['parentThreadID']) && !threadInfo.sourceMessageID && (threadInfo.type === threadTypes.COMMUNITY_OPEN_SUBTHREAD || threadInfo.type === threadTypes.COMMUNITY_SECRET_SUBTHREAD); if (canSeePrivacyTab) { tabs.push(
{errorMessage}
, ); } + if (availableRelationshipActions.length > 0 && otherUserInfo) { + tabs.push( + +
+ +
{errorMessage}
+
+
, + ); + } + const canDeleteThread = hasPermissionForTab(threadInfo, 'delete'); if (canDeleteThread) { tabs.push(
{errorMessage}
, ); } return (
{tabs}
); }, ); export default ConnectedThreadSettingsModal;