Page MenuHomePhorge

D8594.1765133339.diff
No OneTemporary

Size
9 KB
Referenced Files
None
Subscribers
None

D8594.1765133339.diff

diff --git a/web/roles/community-roles-modal.react.js b/web/roles/community-roles-modal.react.js
--- a/web/roles/community-roles-modal.react.js
+++ b/web/roles/community-roles-modal.react.js
@@ -8,6 +8,7 @@
import type { ThreadInfo } from 'lib/types/thread-types.js';
import css from './community-roles-modal.css';
+import CreateRolesModal from './create-roles-modal.react.js';
import RolePanelEntry from './role-panel-entry.react.js';
import Button, { buttonThemes } from '../components/button.react.js';
import Modal from '../modals/modal.react.js';
@@ -18,7 +19,7 @@
};
function CommunityRolesModal(props: CommunityRolesModalProps): React.Node {
- const { popModal } = useModalContext();
+ const { popModal, pushModal } = useModalContext();
const { threadInfo } = props;
const [updatedThreadInfo, setUpdatedThreadInfo] =
@@ -56,7 +57,20 @@
return rolePanelEntries;
}, [roleNamesToMembers]);
- const onClickCreateRole = React.useCallback(() => {}, []);
+ const rolePermissionsForNewRole = React.useMemo(() => [], []);
+
+ const onClickCreateRole = React.useCallback(
+ () =>
+ pushModal(
+ <CreateRolesModal
+ threadInfo={updatedThreadInfo}
+ action="create_role"
+ roleName="New Role"
+ rolePermissions={rolePermissionsForNewRole}
+ />,
+ ),
+ [pushModal, updatedThreadInfo, rolePermissionsForNewRole],
+ );
return (
<Modal name="Roles" onClose={popModal} size="large">
diff --git a/web/roles/create-roles-modal.css b/web/roles/create-roles-modal.css
new file mode 100644
--- /dev/null
+++ b/web/roles/create-roles-modal.css
@@ -0,0 +1,72 @@
+.formContainer {
+ display: flex;
+ flex-direction: column;
+ padding: 16px 32px 0 32px;
+}
+
+.roleNameLabel {
+ color: var(--create-roles-text-color);
+ padding: 4px 0;
+ margin: 20px 0 0 0;
+}
+
+.roleNameInput {
+ display: flex;
+ color: var(--fg);
+ margin: 8px 0 12px 0;
+}
+
+.separator {
+ border: 0;
+ margin: 16px 32px 8px 32px;
+ width: 90%;
+ align-self: center;
+ height: 2px;
+ border: none;
+ border-top: var(--modal-separator) solid 1px;
+}
+
+.permissionsHeaderContainer {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ padding: 16px 32px 24px 32px;
+}
+
+.permissionsLabel {
+ color: var(--create-roles-text-color);
+}
+
+.clearPermissions {
+ font-size: var(--s-font-14);
+}
+
+.clearPermissionsDisabled {
+ color: var(--color-disabled);
+ cursor: not-allowed;
+}
+
+.clearPermissionsEnabled {
+ color: var(--purple-link);
+ 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
new file mode 100644
--- /dev/null
+++ b/web/roles/create-roles-modal.react.js
@@ -0,0 +1,148 @@
+// @flow
+
+import classNames from 'classnames';
+import * as React from 'react';
+
+import { useModalContext } from 'lib/components/modal-provider.react.js';
+import { type UserSurfacedPermission } from 'lib/types/thread-permission-types.js';
+import type { ThreadInfo } from 'lib/types/thread-types.js';
+
+import css from './create-roles-modal.css';
+import {
+ modifyUserSurfacedPermissionOptions,
+ type ModifiedUserSurfacedPermissionOption,
+} from './role-utils.react.js';
+import Button, { buttonThemes } from '../components/button.react.js';
+import EnumSettingsOption from '../components/enum-settings-option.react.js';
+import Input from '../modals/input.react.js';
+import Modal from '../modals/modal.react.js';
+
+type CreateRolesModalProps = {
+ +threadInfo: ThreadInfo,
+ +action: 'create_role' | 'edit_role',
+ +roleName: string,
+ +rolePermissions: $ReadOnlyArray<UserSurfacedPermission>,
+};
+
+function CreateRolesModal(props: CreateRolesModalProps): React.Node {
+ const { popModal } = useModalContext();
+ const { threadInfo, roleName, rolePermissions } = props;
+
+ const [pendingRoleName, setPendingRoleName] =
+ React.useState<string>(roleName);
+ const [pendingRolePermissions, setPendingRolePermissions] =
+ React.useState<$ReadOnlyArray<UserSurfacedPermission>>(rolePermissions);
+
+ const onChangeRoleName = React.useCallback(
+ (event: SyntheticEvent<HTMLInputElement>) => {
+ setPendingRoleName(event.currentTarget.value);
+ },
+ [],
+ );
+
+ const onCloseModal = React.useCallback(() => {
+ popModal();
+ }, [popModal]);
+
+ const clearPermissionsClassNames = classNames({
+ [css.clearPermissions]: true,
+ [css.clearPermissionsDisabled]: pendingRolePermissions.length === 0,
+ [css.clearPermissionsEnabled]: pendingRolePermissions.length > 0,
+ });
+
+ const onClearPermissions = React.useCallback(
+ () => setPendingRolePermissions([]),
+ [],
+ );
+
+ const isUserSurfacedPermissionSelected = React.useCallback(
+ (option: ModifiedUserSurfacedPermissionOption) =>
+ pendingRolePermissions.includes(option.userSurfacedPermission),
+ [pendingRolePermissions],
+ );
+
+ const onEnumValuePress = React.useCallback(
+ (option: ModifiedUserSurfacedPermissionOption) =>
+ setPendingRolePermissions(currentPermissions => {
+ if (currentPermissions.includes(option.userSurfacedPermission)) {
+ return currentPermissions.filter(
+ permission => permission !== option.userSurfacedPermission,
+ );
+ } else {
+ return [...currentPermissions, option.userSurfacedPermission];
+ }
+ }),
+ [],
+ );
+
+ const modifiedUserSurfacedPermissionOptions = React.useMemo(
+ () => modifyUserSurfacedPermissionOptions(threadInfo),
+ [threadInfo],
+ );
+
+ const permissionsList = React.useMemo(
+ () =>
+ [...modifiedUserSurfacedPermissionOptions].map(permission => (
+ <EnumSettingsOption
+ key={permission.userSurfacedPermission}
+ selected={isUserSurfacedPermissionSelected(permission)}
+ onSelect={() => onEnumValuePress(permission)}
+ icon={null}
+ title={permission.title}
+ type="checkbox"
+ statements={permission.statements}
+ />
+ )),
+ [
+ modifiedUserSurfacedPermissionOptions,
+ isUserSurfacedPermissionSelected,
+ onEnumValuePress,
+ ],
+ );
+
+ return (
+ <Modal name="Create Role" onClose={onCloseModal} size="large">
+ <form method="POST" className={css.formContainer}>
+ <div className={css.roleNameLabel}>Role Name</div>
+ <div className={css.roleNameInput}>
+ <Input
+ type="text"
+ value={pendingRoleName}
+ onChange={onChangeRoleName}
+ />
+ </div>
+ </form>
+ <hr className={css.separator} />
+ <div className={css.permissionsHeaderContainer}>
+ <div className={css.permissionsLabel}>Permissions</div>
+ <div
+ className={clearPermissionsClassNames}
+ onClick={onClearPermissions}
+ >
+ Clear Permissions
+ </div>
+ </div>
+ <div className={css.permissionsContainer}>{permissionsList}</div>
+ <div className={css.buttonsContainer}>
+ <Button
+ variant="outline"
+ className={css.backButton}
+ buttonColor={buttonThemes.outline}
+ onClick={null}
+ >
+ Back
+ </Button>
+ <Button
+ variant="filled"
+ className={css.createRoleButton}
+ buttonColor={buttonThemes.standard}
+ onClick={null}
+ >
+ Create
+ </Button>
+ </div>
+ </Modal>
+ );
+}
+
+export default CreateRolesModal;
diff --git a/web/roles/role-utils.react.js b/web/roles/role-utils.react.js
new file mode 100644
--- /dev/null
+++ b/web/roles/role-utils.react.js
@@ -0,0 +1,54 @@
+// @flow
+
+import {
+ type UserSurfacedPermissionOption,
+ userSurfacedPermissionOptions,
+ userSurfacedPermissions,
+} from 'lib/types/thread-permission-types.js';
+import { threadTypes } from 'lib/types/thread-types-enum.js';
+import type { ThreadInfo } from 'lib/types/thread-types.js';
+
+export type ModifiedUserSurfacedPermissionOption = {
+ ...UserSurfacedPermissionOption,
+ statements: $ReadOnlyArray<{
+ +statement: string,
+ +isStatementValid: boolean,
+ +styleStatementBasedOnValidity: boolean,
+ }>,
+};
+function modifyUserSurfacedPermissionOptions(
+ threadInfo: ThreadInfo,
+): $ReadOnlyArray<ModifiedUserSurfacedPermissionOption> {
+ // If the thread is a community announcement root, we want to allow
+ // the option to be voiced in the announcement channels. Otherwise,
+ // we want to remove that option from being configured since this will
+ // be guaranteed on the keyserver.
+ const filteredOptions =
+ threadInfo.type === threadTypes.COMMUNITY_ANNOUNCEMENT_ROOT
+ ? userSurfacedPermissionOptions
+ : [...userSurfacedPermissionOptions].filter(
+ option =>
+ option.userSurfacedPermission !==
+ userSurfacedPermissions.VOICED_IN_ANNOUNCEMENT_CHANNELS,
+ );
+
+ // EnumSettingsOption on web requires a statements array, and since this
+ // is web-specific rather than cross-platform, we should handle this
+ // here and modify the options to include a statement.
+ const modifiedOptions = [...filteredOptions].map(option => {
+ return {
+ ...option,
+ statements: [
+ {
+ statement: option.description,
+ isStatementValid: true,
+ styleStatementBasedOnValidity: false,
+ },
+ ],
+ };
+ });
+
+ return modifiedOptions;
+}
+
+export { modifyUserSurfacedPermissionOptions };
diff --git a/web/theme.css b/web/theme.css
--- a/web/theme.css
+++ b/web/theme.css
@@ -241,4 +241,5 @@
--modal-secondary-label: var(--shades-black-60);
--community-roles-text-color: var(--shades-white-100);
--modal-separator: var(--shades-black-80);
+ --create-roles-text-color: var(--shades-white-100);
}

File Metadata

Mime Type
text/plain
Expires
Sun, Dec 7, 6:48 PM (18 h, 59 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5845542
Default Alt Text
D8594.1765133339.diff (9 KB)

Event Timeline