diff --git a/web/components/community-actions-menu.react.js b/web/components/community-actions-menu.react.js
--- a/web/components/community-actions-menu.react.js
+++ b/web/components/community-actions-menu.react.js
@@ -16,6 +16,7 @@
import ManageInviteLinksModal from '../invite-links/manage-invite-links-modal.react.js';
import ViewInviteLinkModal from '../invite-links/view-invite-link-modal.react.js';
import { useSelector } from '../redux/redux-utils.js';
+import CommunityRolesModal from '../roles/community-roles-modal.react.js';
import { AddLink } from '../vectors.react.js';
type Props = {
@@ -52,7 +53,10 @@
pushModal( );
}, [communityID, pushModal]);
- const openCommunityRolesModal = React.useCallback(() => {}, []);
+ const openCommunityRolesModal = React.useCallback(
+ () => pushModal( ),
+ [communityID, pushModal],
+ );
const items = React.useMemo(() => {
const itemSpecs = [];
diff --git a/web/roles/community-roles-modal.css b/web/roles/community-roles-modal.css
new file mode 100644
--- /dev/null
+++ b/web/roles/community-roles-modal.css
@@ -0,0 +1,52 @@
+.modalDescription {
+ color: var(--modal-fg);
+ padding: 16px 32px 0 32px;
+ font-size: var(--m-font-16);
+}
+
+.rolePanelTitleContainer {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ margin: 32px 32px 16px 32px;
+}
+
+.rolePanelTitle {
+ color: var(--community-roles-text-color);
+ font-size: var(--l-font-18);
+}
+
+.rolePanelTitle:last-of-type {
+ margin-right: 32px;
+}
+
+.separator {
+ border: 0;
+ margin: 0 32px 8px 32px;
+ width: 85%;
+ align-self: center;
+ height: 2px;
+ border: none;
+ border-top: var(--modal-separator) solid 1px;
+}
+
+.rolePanelList {
+ display: flex;
+ flex-direction: column;
+ overflow-y: auto;
+ max-height: 400px;
+ margin: 0 32px 16px 24px;
+}
+
+.createRoleButtonContainer {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-self: center;
+ align-items: stretch;
+ margin-bottom: 16px;
+}
+
+.createRoleButton {
+ margin: 0 32px 0 32px;
+}
diff --git a/web/roles/community-roles-modal.react.js b/web/roles/community-roles-modal.react.js
new file mode 100644
--- /dev/null
+++ b/web/roles/community-roles-modal.react.js
@@ -0,0 +1,77 @@
+// @flow
+
+import * as React from 'react';
+
+import { useModalContext } from 'lib/components/modal-provider.react.js';
+import { threadInfoSelector } from 'lib/selectors/thread-selectors.js';
+import { useRoleMemberCountsForCommunity } from 'lib/shared/thread-utils.js';
+
+import css from './community-roles-modal.css';
+import RolePanelEntry from './role-panel-entry.react.js';
+import Button, { buttonThemes } from '../components/button.react.js';
+import Modal from '../modals/modal.react.js';
+import { useSelector } from '../redux/redux-utils.js';
+
+type CommunityRolesModalProps = {
+ +communityID: string,
+};
+
+function CommunityRolesModal(props: CommunityRolesModalProps): React.Node {
+ const { popModal } = useModalContext();
+ const { communityID } = props;
+
+ const threadInfo = useSelector(
+ state => threadInfoSelector(state)[communityID],
+ );
+
+ const roleNamesToMembers = useRoleMemberCountsForCommunity(threadInfo);
+
+ const rolePanelList = React.useMemo(() => {
+ const rolePanelEntries = [];
+
+ Object.keys(roleNamesToMembers).forEach(roleName => {
+ rolePanelEntries.push(
+ ,
+ );
+ });
+
+ return rolePanelEntries;
+ }, [roleNamesToMembers]);
+
+ const onClickCreateRole = React.useCallback(() => {}, []);
+
+ return (
+
+
+ Roles help you group community members together and assign them certain
+ permissions. When people join the community, they are automatically
+ assigned the Members role.
+
+
+ Communities must always have the Admins and Members role.
+
+
+
+ {rolePanelList}
+
+
+ Create Role
+
+
+
+ );
+}
+
+export default CommunityRolesModal;
diff --git a/web/roles/role-panel-entry.css b/web/roles/role-panel-entry.css
new file mode 100644
--- /dev/null
+++ b/web/roles/role-panel-entry.css
@@ -0,0 +1,24 @@
+.rolePanelEntry {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ padding: 12px;
+ color: var(--community-roles-text-color);
+ font-weight: 500;
+}
+
+.rolePanelNameEntry {
+ font-size: var(--s-font-14);
+}
+
+.rolePanelCountEntryContainer {
+ margin-right: 40px;
+ display: flex;
+ align-items: flex-end;
+}
+
+.rolePanelCountEntry {
+ font-size: var(--s-font-14);
+ margin-right: 2px;
+}
diff --git a/web/roles/role-panel-entry.react.js b/web/roles/role-panel-entry.react.js
new file mode 100644
--- /dev/null
+++ b/web/roles/role-panel-entry.react.js
@@ -0,0 +1,26 @@
+// @flow
+
+import * as React from 'react';
+
+import css from './role-panel-entry.css';
+import CommIcon from '../CommIcon.react.js';
+
+type RolePanelEntryProps = {
+ +roleName: string,
+ +memberCount: number,
+};
+
+function RolePanelEntry(props: RolePanelEntryProps): React.Node {
+ const { roleName, memberCount } = props;
+ return (
+
+ );
+}
+
+export default RolePanelEntry;
diff --git a/web/theme.css b/web/theme.css
--- a/web/theme.css
+++ b/web/theme.css
@@ -239,4 +239,6 @@
--change-member-role-modal-disabled-background: var(--shades-black-80);
--unsaved-changes-modal-text-color: var(--shades-white-60);
--modal-secondary-label: var(--shades-black-60);
+ --community-roles-text-color: var(--shades-white-100);
+ --modal-separator: var(--shades-black-80);
}