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(), + [community, 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,91 @@ +// @flow + +import * as React from 'react'; + +import { useModalContext } from 'lib/components/modal-provider.react.js'; +import { threadInfoSelector } from 'lib/selectors/thread-selectors.js'; +import { getRoleMemberCountsForCommunity } from 'lib/shared/thread-utils.js'; +import type { ThreadInfo } from 'lib/types/thread-types.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 = { + +threadInfo: ThreadInfo, +}; + +function CommunityRolesModal(props: CommunityRolesModalProps): React.Node { + const { popModal } = useModalContext(); + const { threadInfo } = props; + + const [updatedThreadInfo, setUpdatedThreadInfo] = + React.useState(threadInfo); + + const threadID = threadInfo.id; + const reduxThreadInfo: ?ThreadInfo = useSelector( + state => threadInfoSelector(state)[threadID], + ); + + React.useEffect(() => { + if (reduxThreadInfo) { + setUpdatedThreadInfo(reduxThreadInfo); + } + }, [reduxThreadInfo]); + + const roleNamesToMembers = React.useMemo( + () => getRoleMemberCountsForCommunity(updatedThreadInfo), + [updatedThreadInfo], + ); + + 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. +
+
+
Roles
+
Members
+
+
+
{rolePanelList}
+
+ +
+
+ ); +} + +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 ( +
+
{roleName}
+
+
{memberCount}
+ +
+
+ ); +} + +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); }