diff --git a/web/modals/threads/members/change-member-role-modal.css b/web/modals/threads/members/change-member-role-modal.css --- a/web/modals/threads/members/change-member-role-modal.css +++ b/web/modals/threads/members/change-member-role-modal.css @@ -48,3 +48,9 @@ .roleModalBackButton { padding: 14px 32px; } + +.roleModalErrorMessage { + color: var(--error); + display: flex; + justify-content: center; +} diff --git a/web/modals/threads/members/change-member-role-modal.react.js b/web/modals/threads/members/change-member-role-modal.react.js --- a/web/modals/threads/members/change-member-role-modal.react.js +++ b/web/modals/threads/members/change-member-role-modal.react.js @@ -8,6 +8,8 @@ changeThreadMemberRolesActionTypes, } from 'lib/actions/thread-actions.js'; import { useModalContext } from 'lib/components/modal-provider.react.js'; +import { otherUsersButNoOtherAdmins } from 'lib/selectors/thread-selectors.js'; +import { roleIsAdminRole } from 'lib/shared/thread-utils.js'; import type { RelativeMemberInfo, ThreadInfo } from 'lib/types/thread-types'; import { useDispatchActionPromise, @@ -19,6 +21,7 @@ import Button, { buttonThemes } from '../../../components/button.react.js'; import Dropdown from '../../../components/dropdown.react.js'; import UserAvatar from '../../../components/user-avatar.react.js'; +import { useSelector } from '../../../redux/redux-utils.js'; import Modal from '../../modal.react.js'; import UnsavedChangesModal from '../../unsaved-changes-modal.react.js'; @@ -32,6 +35,10 @@ const { pushModal, popModal } = useModalContext(); const dispatchActionPromise = useDispatchActionPromise(); const callChangeThreadMemberRoles = useServerCall(changeThreadMemberRoles); + const [showErrorMessage, setShowErrorMessage] = React.useState(false); + const otherUsersButNoOtherAdminsValue = useSelector( + otherUsersButNoOtherAdmins(threadInfo.id), + ); const roleOptions = React.useMemo( () => @@ -56,12 +63,39 @@ pushModal(); }, [initialSelectedRole, popModal, pushModal, selectedRole]); + const errorMessage = React.useMemo(() => { + if (!showErrorMessage) { + return null; + } + + return ( + <> +
Cannot change role.
+
+ There must be at least one admin at any given time. +
+ + ); + }, [showErrorMessage]); + const onSave = React.useCallback(() => { if (selectedRole === initialSelectedRole) { popModal(); return; } + const memberIsAdmin = roleIsAdminRole( + threadInfo.roles[initialSelectedRole], + ); + + // Handle case where the sole admin's role is being changed. + if (otherUsersButNoOtherAdminsValue && memberIsAdmin) { + setShowErrorMessage(true); + return; + } + + setShowErrorMessage(false); + const createChangeThreadMemberRolesPromise = () => { return callChangeThreadMemberRoles( threadInfo.id, @@ -77,12 +111,13 @@ popModal(); }, [ callChangeThreadMemberRoles, + otherUsersButNoOtherAdminsValue, dispatchActionPromise, initialSelectedRole, - memberInfo.id, + memberInfo, popModal, selectedRole, - threadInfo.id, + threadInfo, ]); return ( @@ -104,6 +139,7 @@ setActiveSelection={setSelectedRole} /> + {errorMessage}