From 3a41de22466c8c6c54703d78f1f3f12f41803dd4 Mon Sep 17 00:00:00 2001 From: Youssef Khedher Date: Mon, 17 Jan 2022 11:41:44 +0100 Subject: [PATCH] feat/update project access (#571) * feat: add user guidance in project access tab * feat: add role description to the menu list * feat: add tooltip to delete button * feat: add role description to add user menu * feat: auto select user when there is only one option * fix: refactor role select * fix: remove minwidth from form control Co-authored-by: Fredrik Oseberg --- .../ProjectAccess/ProjectAccess.styles.ts | 33 ++++ .../ProjectAccess.tsx} | 140 ++++++--------- .../ProjectRoleSelect/ProjectRoleSelect.tsx | 70 ++++++++ .../src/component/project/access-add-user.js | 168 +++++++++--------- .../src/component/project/access-container.js | 6 +- 5 files changed, 246 insertions(+), 171 deletions(-) create mode 100644 frontend/src/component/project/ProjectAccess/ProjectAccess.styles.ts rename frontend/src/component/project/{access-component.js => ProjectAccess/ProjectAccess.tsx} (53%) create mode 100644 frontend/src/component/project/ProjectAccess/ProjectRoleSelect/ProjectRoleSelect.tsx diff --git a/frontend/src/component/project/ProjectAccess/ProjectAccess.styles.ts b/frontend/src/component/project/ProjectAccess/ProjectAccess.styles.ts new file mode 100644 index 0000000000..388a9a9da1 --- /dev/null +++ b/frontend/src/component/project/ProjectAccess/ProjectAccess.styles.ts @@ -0,0 +1,33 @@ +import { makeStyles } from '@material-ui/core/styles'; + +export const useStyles = makeStyles(theme => ({ + pageContent: { + minHeight: '200px', + }, + divider: { + height: '1px', + width: '106.65%', + marginLeft: '-2rem', + backgroundColor: '#efefef', + marginTop: '2rem', + }, + actionList: { + display: 'flex', + alignItems: 'center', + }, + inputLabel: { backgroundColor: '#fff' }, + roleName: { + fontWeight: 'bold', + padding: '5px 0px', + }, + iconButton: { + marginLeft: '0.5rem', + }, + menuItem: { + width: '340px', + whiteSpace: 'normal', + }, + projectRoleSelect: { + minWidth: '150px', + }, +})); diff --git a/frontend/src/component/project/access-component.js b/frontend/src/component/project/ProjectAccess/ProjectAccess.tsx similarity index 53% rename from frontend/src/component/project/access-component.js rename to frontend/src/component/project/ProjectAccess/ProjectAccess.tsx index b169c6b88e..71f6c1635c 100644 --- a/frontend/src/component/project/access-component.js +++ b/frontend/src/component/project/ProjectAccess/ProjectAccess.tsx @@ -1,6 +1,5 @@ /* eslint-disable react/jsx-no-target-blank */ import { useEffect, useState } from 'react'; -import PropTypes from 'prop-types'; import { Avatar, Button, @@ -9,28 +8,30 @@ import { DialogContent, DialogContentText, DialogTitle, - InputLabel, - IconButton, List, ListItem, ListItemAvatar, ListItemSecondaryAction, ListItemText, MenuItem, - Select, - FormControl, } from '@material-ui/core'; import { Delete } from '@material-ui/icons'; import { Alert } from '@material-ui/lab'; -import AddUserComponent from './access-add-user'; +import AddUserComponent from '../access-add-user'; -import projectApi from '../../store/project/api'; -import PageContent from '../common/PageContent'; -import useUiConfig from '../../hooks/api/getters/useUiConfig/useUiConfig'; +import projectApi from '../../../store/project/api'; +import PageContent from '../../common/PageContent'; +import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig'; +import { useStyles } from './ProjectAccess.styles'; +import PermissionIconButton from '../../common/PermissionIconButton/PermissionIconButton'; +import { useParams } from 'react-router-dom'; +import { IFeatureViewParams } from '../../../interfaces/params'; +import ProjectRoleSelect from './ProjectRoleSelect/ProjectRoleSelect'; - -function AccessComponent({ projectId, project }) { +const ProjectAccess = () => { + const { id } = useParams(); + const styles = useStyles(); const [roles, setRoles] = useState([]); const [users, setUsers] = useState([]); const [error, setError] = useState(); @@ -39,13 +40,11 @@ function AccessComponent({ projectId, project }) { useEffect(() => { fetchAccess(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [projectId]); - - + }, [id]); const fetchAccess = async () => { try { - const access = await projectApi.fetchAccess(projectId); + const access = await projectApi.fetchAccess(id); setRoles(access.roles); setUsers( access.users.map(u => ({ ...u, name: u.name || '(No name)' })) @@ -57,19 +56,24 @@ function AccessComponent({ projectId, project }) { if (isOss()) { return ( - - - Controlling access to projects requires a paid version of Unleash. - Check out getunleash.io to find out more. - - ); + + + Controlling access to projects requires a paid version of + Unleash. Check out{' '} + + getunleash.io + {' '} + to find out more. + + + ); } const handleRoleChange = (userId, currRoleId) => async evt => { const roleId = evt.target.value; try { - await projectApi.removeUserFromRole(projectId, currRoleId, userId); - await projectApi.addUserToRole(projectId, roleId, userId); + await projectApi.removeUserFromRole(id, currRoleId, userId); + await projectApi.addUserToRole(id, roleId, userId); const newUsers = users.map(u => { if (u.id === userId) { return { ...u, roleId }; @@ -83,7 +87,7 @@ function AccessComponent({ projectId, project }) { const addUser = async (userId, roleId) => { try { - await projectApi.addUserToRole(projectId, roleId, userId); + await projectApi.addUserToRole(id, roleId, userId); await fetchAccess(); } catch (err) { setError(err.message || 'Server problems when adding users.'); @@ -92,7 +96,7 @@ function AccessComponent({ projectId, project }) { const removeAccess = (userId, roleId) => async () => { try { - await projectApi.removeUserFromRole(projectId, roleId, userId); + await projectApi.removeUserFromRole(id, roleId, userId); const newUsers = users.filter(u => u.id !== userId); setUsers(newUsers); } catch (err) { @@ -105,9 +109,7 @@ function AccessComponent({ projectId, project }) { }; return ( - + -
+
{users.map(user => { const labelId = `checkbox-list-secondary-label-${user.id}`; @@ -154,51 +148,40 @@ function AccessComponent({ projectId, project }) { secondary={user.email || user.username} /> - - - Role - - - - + + Choose role + + + + - + ); @@ -206,11 +189,6 @@ function AccessComponent({ projectId, project }) {
); -} - -AccessComponent.propTypes = { - projectId: PropTypes.string.isRequired, - project: PropTypes.object, }; -export default AccessComponent; +export default ProjectAccess; diff --git a/frontend/src/component/project/ProjectAccess/ProjectRoleSelect/ProjectRoleSelect.tsx b/frontend/src/component/project/ProjectAccess/ProjectRoleSelect/ProjectRoleSelect.tsx new file mode 100644 index 0000000000..deae3eda52 --- /dev/null +++ b/frontend/src/component/project/ProjectAccess/ProjectRoleSelect/ProjectRoleSelect.tsx @@ -0,0 +1,70 @@ +import { FormControl, InputLabel, Select, MenuItem } from '@material-ui/core'; +import React from 'react'; +import IRole from '../../../../interfaces/role'; + +import { useStyles } from '../ProjectAccess.styles'; + +interface IProjectRoleSelect { + roles: IRole[]; + labelId: string; + id: string; + placeholder?: string; + onChange: () => void; + value: any; +} + +const ProjectRoleSelect: React.FC = ({ + roles, + onChange, + labelId, + id, + value, + placeholder, + children, +}) => { + const styles = useStyles(); + return ( + + + Role + + + + ); +}; + +export default ProjectRoleSelect; diff --git a/frontend/src/component/project/access-add-user.js b/frontend/src/component/project/access-add-user.js index 1277f95e4d..f6cab53ef1 100644 --- a/frontend/src/component/project/access-add-user.js +++ b/frontend/src/component/project/access-add-user.js @@ -2,24 +2,23 @@ import React, { useEffect, useState } from 'react'; import projectApi from '../../store/project/api'; import PropTypes from 'prop-types'; import { - Select, - MenuItem, TextField, CircularProgress, - InputLabel, - FormControl, Grid, Button, InputAdornment, } from '@material-ui/core'; import { Search } from '@material-ui/icons'; import Autocomplete from '@material-ui/lab/Autocomplete'; +import { Alert } from '@material-ui/lab'; +import ProjectRoleSelect from './ProjectAccess/ProjectRoleSelect/ProjectRoleSelect'; function AddUserComponent({ roles, addUserToRole }) { const [user, setUser] = useState(); const [role, setRole] = useState({}); const [options, setOptions] = useState([]); const [loading, setLoading] = useState(false); + const [select, setSelect] = useState(false); useEffect(() => { if (roles.length > 0) { @@ -39,13 +38,17 @@ function AddUserComponent({ roles, addUserToRole }) { } else { setOptions([]); } - setLoading(false); }; const handleQueryUpdate = evt => { const q = evt.target.value; search(q); + if (options.length === 1) { + setSelect(true); + return; + } + setSelect(false); }; const handleSelectUser = (evt, value) => { @@ -67,96 +70,85 @@ function AddUserComponent({ roles, addUserToRole }) { }; return ( - - - true} - filterOptions={o => o} - getOptionLabel={option => { - if (option) { - return `${option.name || '(Empty name)'} <${ - option.email || option.username - }>`; - } else return ''; - }} - options={options} - loading={loading} - renderInput={params => ( - - - - ), - endAdornment: ( - - {loading ? ( - - ) : null} - {params.InputProps.endAdornment} - - ), - }} - /> - )} - /> - - - - - Role - - - + Add user + + - - - - + ); } diff --git a/frontend/src/component/project/access-container.js b/frontend/src/component/project/access-container.js index dd7117611e..692a07f425 100644 --- a/frontend/src/component/project/access-container.js +++ b/frontend/src/component/project/access-container.js @@ -1,9 +1,11 @@ import { connect } from 'react-redux'; -import Component from './access-component'; +import Component from './ProjectAccess/ProjectAccess'; const mapStateToProps = (state, props) => { const projectBase = { id: '', name: '', description: '' }; - const realProject = state.projects.toJS().find(n => n.id === props.projectId); + const realProject = state.projects + .toJS() + .find(n => n.id === props.projectId); const project = Object.assign(projectBase, realProject); return {