diff --git a/web/modals/threads/settings/submit-section.css b/web/modals/threads/settings/submit-section.css deleted file mode 100644 index 216c11ee0..000000000 --- a/web/modals/threads/settings/submit-section.css +++ /dev/null @@ -1,22 +0,0 @@ -.container { - height: 100%; - width: 100%; - display: flex; - flex-direction: column; -} - -.error { - text-align: center; - font-size: 16px; - color: var(--error); - font-style: italic; - - margin-top: auto; - padding: 5px; -} - -.button { - width: 100%; - align-self: flex-end; - min-height: 45px; -} diff --git a/web/modals/threads/settings/submit-section.react.js b/web/modals/threads/settings/submit-section.react.js deleted file mode 100644 index f2afdf758..000000000 --- a/web/modals/threads/settings/submit-section.react.js +++ /dev/null @@ -1,46 +0,0 @@ -// @flow - -import classnames from 'classnames'; -import * as React from 'react'; - -import css from './submit-section.css'; -import type { ButtonProps } from '../../../components/button.react.js'; -import Button from '../../../components/button.react.js'; - -type Props = { - ...ButtonProps, - +errorMessage?: ?string, - +containerClassName?: string, -}; - -function SubmitSection(props: Props): React.Node { - const { - children, - containerClassName = '', - errorMessage, - onClick, - variant, - disabled = false, - className = '', - } = props; - - const containerStyle = classnames(css.container, containerClassName); - const buttonStyle = classnames(css.button, className); - - return ( -
-
{errorMessage}
- -
- ); -} - -export default SubmitSection; diff --git a/web/modals/threads/settings/thread-settings-modal.css b/web/modals/threads/settings/thread-settings-modal.css index ff30c15a7..678cb873d 100644 --- a/web/modals/threads/settings/thread-settings-modal.css +++ b/web/modals/threads/settings/thread-settings-modal.css @@ -1,28 +1,35 @@ div.modal_body { display: flex; flex-direction: column; width: 383px; height: 60vh; overflow: hidden; } div.tab_body { overflow: auto; flex: 1; display: flex; flex-direction: column; } div.modal_form_error { display: flex; align-items: flex-end; justify-content: center; font-size: var(--s-font-16); color: var(--error); font-style: italic; min-height: 26px; flex: 1; padding: 5px; } .relationshipPrimaryButtonPlaceholder { height: 44.5px; } + +.errorMessage { + color: var(--text-background-danger-default); + font-style: italic; + margin-top: 8px; + text-align: center; +} diff --git a/web/modals/threads/settings/thread-settings-modal.react.js b/web/modals/threads/settings/thread-settings-modal.react.js index 76fcbf836..407d0b2d9 100644 --- a/web/modals/threads/settings/thread-settings-modal.react.js +++ b/web/modals/threads/settings/thread-settings-modal.react.js @@ -1,292 +1,292 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import { changeThreadSettingsActionTypes, deleteThreadActionTypes, } from 'lib/actions/thread-actions.js'; import { useModalContext } from 'lib/components/modal-provider.react.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import { threadInfoSelector } from 'lib/selectors/thread-selectors.js'; import { getAvailableRelationshipButtons } from 'lib/shared/relationship-utils.js'; import { getSingleOtherUser, threadHasPermission, threadUIName, } from 'lib/shared/thread-utils.js'; import type { ThreadInfo } from 'lib/types/minimally-encoded-thread-permissions-types.js'; import type { RelationshipButton } from 'lib/types/relationship-types.js'; import { threadPermissions } from 'lib/types/thread-permission-types.js'; import { threadTypes } from 'lib/types/thread-types-enum.js'; import { type ThreadChanges } from 'lib/types/thread-types.js'; import { useResolvedThreadInfo } from 'lib/utils/entity-helpers.js'; import ThreadSettingsDeleteButton from './thread-settings-delete-button.react.js'; import ThreadSettingsDeleteTab from './thread-settings-delete-tab.react.js'; import ThreadSettingsGeneralTab from './thread-settings-general-tab.react.js'; import css from './thread-settings-modal.css'; import ThreadSettingsPrivacyTab from './thread-settings-privacy-tab.react.js'; import ThreadSettingsRelationshipTab from './thread-settings-relationship-tab.react.js'; import ThreadSettingsSaveButton from './thread-settings-save-button.react.js'; import Tabs, { type TabData } from '../../../components/tabs.react.js'; import { useSelector } from '../../../redux/redux-utils.js'; import Modal from '../../modal.react.js'; 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 threadInfo: ?ThreadInfo = useSelector( state => threadInfoSelector(state)[props.threadID], ); const modalContext = useModalContext(); - // eslint-disable-next-line no-unused-vars const [errorMessage, setErrorMessage] = React.useState(''); const [currentTabType, setCurrentTabType] = React.useState('general'); const [queuedChanges, setQueuedChanges] = React.useState( Object.freeze({}), ); const threadInfoWithNoName = React.useMemo(() => { invariant(threadInfo, 'threadInfo should exist in threadInfoWithNoName'); if (threadInfo.name === null || threadInfo.name === undefined) { return threadInfo; } const withNoName = { ...threadInfo, name: undefined }; return { ...withNoName, uiName: threadUIName(withNoName), }; }, [threadInfo]); const resolvedThreadInfo = useResolvedThreadInfo(threadInfoWithNoName); const namePlaceholder = resolvedThreadInfo.uiName; const viewerID = useSelector( state => state.currentUserInfo && state.currentUserInfo.id, ); const userInfos = useSelector(state => state.userStore.userInfos); 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 ([]: RelationshipButton[]); } return getAvailableRelationshipButtons(otherUserInfo); }, [otherUserInfo]); const hasPermissionForTab = React.useCallback( // ESLint doesn't recognize that invariant always throws // eslint-disable-next-line consistent-return (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]); React.useEffect(() => () => setErrorMessage(''), [currentTabType]); const tabsData: $ReadOnlyArray> = React.useMemo(() => { if (!threadInfo) { return []; } const result = [{ id: 'general', header: 'General' }]; // 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) { result.push({ id: 'privacy', header: 'Privacy' }); } if (availableRelationshipActions.length > 0 && otherUserInfo) { result.push({ id: 'relationship', header: 'Relationship' }); } if (hasPermissionForTab(threadInfo, 'delete')) { result.push({ id: 'delete', header: 'Delete' }); } return result; }, [ availableRelationshipActions.length, hasPermissionForTab, otherUserInfo, queuedChanges, threadInfo, ]); const tabs = React.useMemo( () => ( ), [currentTabType, tabsData], ); const tabContent = React.useMemo(() => { if (!threadInfo) { return null; } if (currentTabType === 'general') { return ( ); } if (currentTabType === 'privacy') { return ( ); } if (currentTabType === 'relationship') { invariant(otherUserInfo, 'otherUserInfo should be set'); return ( ); } return ; }, [ availableRelationshipActions, changeInProgress, currentTabType, namePlaceholder, otherUserInfo, queuedChanges, threadInfo, ]); const primaryButton = React.useMemo(() => { if (!threadInfo) { return null; } if (currentTabType === 'general' || currentTabType === 'privacy') { return ( ); } if (currentTabType === 'relationship') { return
; } return ( ); }, [changeInProgress, currentTabType, queuedChanges, threadInfo]); if (!threadInfo) { return (

You no longer have permission to view this chat

); } return (
{tabContent}
+
{errorMessage}
); }); export default ConnectedThreadSettingsModal;