diff --git a/web/roles/create-roles-modal.css b/web/roles/create-roles-modal.css index 82ddd5431..1e5d756ff 100644 --- a/web/roles/create-roles-modal.css +++ b/web/roles/create-roles-modal.css @@ -1,82 +1,73 @@ .formContainer { display: flex; flex-direction: column; - padding: 16px 32px 0 32px; } .roleNameLabel { color: var(--text-background-primary-default); padding: 4px 0; margin-top: 20px; } .roleNameInput { display: flex; margin: 8px 0 12px 0; } .errorMessage { color: var(--text-background-danger-default); font-size: var(--s-font-14); align-self: center; visibility: hidden; } .errorMessageVisible { visibility: visible; } .separator { border: 0; - margin: 16px 32px 8px 32px; - width: 90%; + margin: 16px 0 8px 0; align-self: center; height: 2px; border: none; border-top: var(--separator-background-primary-default) solid 1px; } .permissionsHeaderContainer { display: flex; flex-direction: row; justify-content: space-between; - padding: 16px 32px 24px 32px; + padding: 16px 0 24px 0; } .permissionsLabel { color: var(--text-background-primary-default); } .clearPermissions { font-size: var(--s-font-14); } .clearPermissionsDisabled { color: var(--link-background-primary-disabled); cursor: not-allowed; } .clearPermissionsEnabled { color: var(--link-background-primary-default); cursor: pointer; } .permissionsContainer { max-height: 350px; overflow-y: auto; } -.buttonsContainer { - display: flex; - flex-direction: row; - justify-content: flex-end; - padding: 16px 32px; -} - .backButton { width: 100px; } .createRoleButton { margin-left: 8px; } diff --git a/web/roles/create-roles-modal.react.js b/web/roles/create-roles-modal.react.js index bcb1e0b78..a37edcb40 100644 --- a/web/roles/create-roles-modal.react.js +++ b/web/roles/create-roles-modal.react.js @@ -1,284 +1,300 @@ // @flow import classNames from 'classnames'; import invariant from 'invariant'; import * as React from 'react'; import { modifyCommunityRoleActionTypes, useModifyCommunityRole, } from 'lib/actions/thread-actions.js'; import { useModalContext } from 'lib/components/modal-provider.react.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import type { LoadingStatus } from 'lib/types/loading-types.js'; import type { ThreadInfo } from 'lib/types/minimally-encoded-thread-permissions-types.js'; import { type UserSurfacedPermission, type UserSurfacedPermissionOption, userSurfacedPermissionOptions, } from 'lib/types/thread-permission-types.js'; import type { RoleModificationRequest } from 'lib/types/thread-types.js'; import { values } from 'lib/utils/objects.js'; import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; import css from './create-roles-modal.css'; import Button, { buttonThemes } from '../components/button.react.js'; import EnumSettingsOption from '../components/enum-settings-option.react.js'; import LoadingIndicator from '../loading-indicator.react.js'; import Input from '../modals/input.react.js'; import Modal from '../modals/modal.react.js'; import UnsavedChangesModal from '../modals/unsaved-changes-modal.react.js'; import { useSelector } from '../redux/redux-utils.js'; const createRolesLoadingStatusSelector = createLoadingStatusSelector( modifyCommunityRoleActionTypes, ); type CreateRolesModalProps = { +threadInfo: ThreadInfo, +action: 'create_role' | 'edit_role', +existingRoleID?: string, +roleName: string, +rolePermissions: $ReadOnlySet, }; type RoleCreationErrorVariant = 'already_exists' | 'unknown_error'; function CreateRolesModal(props: CreateRolesModalProps): React.Node { const { pushModal, popModal } = useModalContext(); const { threadInfo, action, existingRoleID, roleName, rolePermissions } = props; const modalName = action === 'create_role' ? 'Create role' : 'Edit role'; const callModifyCommunityRole = useModifyCommunityRole(); const dispatchActionPromise = useDispatchActionPromise(); const createRolesLoadingStatus: LoadingStatus = useSelector( createRolesLoadingStatusSelector, ); const [pendingRoleName, setPendingRoleName] = React.useState(roleName); const [pendingRolePermissions, setPendingRolePermissions] = React.useState<$ReadOnlySet>(rolePermissions); const [roleCreationFailed, setRoleCreationFailed] = React.useState(); const createButtonText = action === 'create_role' ? 'Create' : 'Save'; const onChangeRoleName = React.useCallback( (event: SyntheticEvent) => { setRoleCreationFailed(null); setPendingRoleName(event.currentTarget.value); }, [], ); const onCloseModal = React.useCallback(() => { const pendingSet = new Set(pendingRolePermissions); const roleSet = new Set(rolePermissions); let arePermissionsEqual = true; if (pendingSet.size !== roleSet.size) { arePermissionsEqual = false; } for (const permission of pendingSet) { if (!roleSet.has(permission)) { arePermissionsEqual = false; break; } } if (pendingRoleName === roleName && arePermissionsEqual) { popModal(); return; } pushModal(); }, [ pendingRoleName, roleName, pendingRolePermissions, rolePermissions, pushModal, popModal, ]); const clearPermissionsClassNames = classNames({ [css.clearPermissions]: true, [css.clearPermissionsDisabled]: pendingRolePermissions.size === 0, [css.clearPermissionsEnabled]: pendingRolePermissions.size > 0, }); const onClearPermissions = React.useCallback( () => setPendingRolePermissions(new Set()), [], ); const isUserSurfacedPermissionSelected = React.useCallback( (option: UserSurfacedPermissionOption) => pendingRolePermissions.has(option.userSurfacedPermission), [pendingRolePermissions], ); const onEnumValuePress = React.useCallback( (option: UserSurfacedPermissionOption) => setPendingRolePermissions(currentPermissions => { if (currentPermissions.has(option.userSurfacedPermission)) { const newPermissions = new Set(currentPermissions); newPermissions.delete(option.userSurfacedPermission); return newPermissions; } else { return new Set([ ...currentPermissions, option.userSurfacedPermission, ]); } }), [], ); const permissionsList = React.useMemo( () => [...userSurfacedPermissionOptions].map(permission => ( onEnumValuePress(permission)} icon={null} title={permission.title} type="checkbox" statements={[{ statement: permission.description }]} /> )), [isUserSurfacedPermissionSelected, onEnumValuePress], ); const errorMessageClassNames = classNames({ [css.errorMessage]: true, [css.errorMessageVisible]: !!roleCreationFailed, }); const threadRoleNames = React.useMemo( () => values(threadInfo.roles).map(role => role.name), [threadInfo], ); const onClickCreateRole = React.useCallback(() => { if (threadRoleNames.includes(pendingRoleName) && action === 'create_role') { setRoleCreationFailed('already_exists'); return; } let callModifyCommunityRoleParams: RoleModificationRequest; if (action === 'create_role') { callModifyCommunityRoleParams = { community: threadInfo.id, action, name: pendingRoleName, permissions: [...pendingRolePermissions], }; } else { invariant(existingRoleID, 'existingRoleID should be defined'); callModifyCommunityRoleParams = { community: threadInfo.id, existingRoleID, action, name: pendingRoleName, permissions: [...pendingRolePermissions], }; } void dispatchActionPromise( modifyCommunityRoleActionTypes, (async () => { try { const response = await callModifyCommunityRole( callModifyCommunityRoleParams, ); popModal(); return response; } catch (e) { setRoleCreationFailed('unknown_error'); throw e; } })(), ); }, [ callModifyCommunityRole, dispatchActionPromise, threadInfo, action, existingRoleID, pendingRoleName, pendingRolePermissions, popModal, threadRoleNames, ]); const errorMessage = React.useMemo(() => { if (roleCreationFailed === 'already_exists') { return 'There is already a role with this name in the community'; } else { return 'An unknown error occurred. Please try again'; } }, [roleCreationFailed]); const saveButtonContent = React.useMemo(() => { if (createRolesLoadingStatus === 'loading') { return ( ); } return createButtonText; }, [createRolesLoadingStatus, createButtonText]); + const primaryButton = React.useMemo( + () => ( + + ), + [onClickCreateRole, saveButtonContent], + ); + + const secondaryButton = React.useMemo( + () => ( + + ), + [onCloseModal], + ); + return ( - +
Role name
{errorMessage}

Permissions
Clear Permissions
{permissionsList}
-
- - -
); } export default CreateRolesModal;