Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F32210929
D8594.1765133339.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
9 KB
Referenced Files
None
Subscribers
None
D8594.1765133339.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D8594: [web] Create a 'create role' modal to allow users to make custom roles
Attached
Detach File
Event Timeline
Log In to Comment