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;