diff --git a/frontend/src/component/admin/groups/Group/Group.tsx b/frontend/src/component/admin/groups/Group/Group.tsx index d7ccb7d91c..a3330c49e6 100644 --- a/frontend/src/component/admin/groups/Group/Group.tsx +++ b/frontend/src/component/admin/groups/Group/Group.tsx @@ -255,7 +255,7 @@ export const Group: VFC = () => { onClick={() => setRemoveOpen(true)} permission={ADMIN} tooltipProps={{ - title: 'Remove group', + title: 'Delete group', }} > diff --git a/frontend/src/component/admin/groups/GroupForm/GroupForm.tsx b/frontend/src/component/admin/groups/GroupForm/GroupForm.tsx index d5ef3190c3..6e6e2d9bb2 100644 --- a/frontend/src/component/admin/groups/GroupForm/GroupForm.tsx +++ b/frontend/src/component/admin/groups/GroupForm/GroupForm.tsx @@ -81,6 +81,7 @@ export const GroupForm: FC = ({ value={name} onChange={e => setName(e.target.value)} data-testid={UG_NAME_ID} + required /> How would you describe your group? diff --git a/frontend/src/component/admin/groups/GroupUserRoleCell/GroupUserRoleCell.tsx b/frontend/src/component/admin/groups/GroupUserRoleCell/GroupUserRoleCell.tsx index 3d26d2d3a5..6382e56dc7 100644 --- a/frontend/src/component/admin/groups/GroupUserRoleCell/GroupUserRoleCell.tsx +++ b/frontend/src/component/admin/groups/GroupUserRoleCell/GroupUserRoleCell.tsx @@ -2,25 +2,11 @@ import { capitalize, MenuItem, Select, styled } from '@mui/material'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { TextCell } from 'component/common/Table/cells/TextCell/TextCell'; import { Role } from 'interfaces/group'; +import { Badge } from 'component/common/Badge/Badge'; +import { StarRounded } from '@mui/icons-material'; -const StyledBadge = styled('div')(({ theme }) => ({ - padding: theme.spacing(0.5, 1), - textDecoration: 'none', - color: theme.palette.text.secondary, - border: `1px solid ${theme.palette.dividerAlternative}`, - background: theme.palette.activityIndicators.unknown, - display: 'inline-block', - borderRadius: theme.shape.borderRadius, - marginLeft: theme.spacing(1.5), - fontSize: theme.fontSizes.smallerBody, - fontWeight: theme.fontWeight.bold, - lineHeight: 1, -})); - -const StyledOwnerBadge = styled(StyledBadge)(({ theme }) => ({ - color: theme.palette.success.dark, - border: `1px solid ${theme.palette.success.border}`, - background: theme.palette.success.light, +const StyledPopupStar = styled(StarRounded)(({ theme }) => ({ + color: theme.palette.warning.main, })); interface IGroupUserRoleCellProps { @@ -35,8 +21,12 @@ export const GroupUserRoleCell = ({ const renderBadge = () => ( {capitalize(value)}} - elseShow={{capitalize(value)}} + show={{capitalize(value)}} + elseShow={ + }> + {capitalize(value)} + + } /> ); diff --git a/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCard.tsx b/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCard.tsx index 8373b42d2e..6f9309b624 100644 --- a/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCard.tsx +++ b/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCard.tsx @@ -1,6 +1,6 @@ import { styled, Tooltip } from '@mui/material'; import { IGroup } from 'interfaces/group'; -import { Link } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { GroupCardAvatars } from './GroupCardAvatars/GroupCardAvatars'; import { Badge } from 'component/common/Badge/Badge'; @@ -20,9 +20,15 @@ const StyledGroupCard = styled('aside')(({ theme }) => ({ border: `1px solid ${theme.palette.dividerAlternative}`, borderRadius: theme.shape.borderRadiusLarge, boxShadow: theme.boxShadows.card, + display: 'flex', + flexDirection: 'column', [theme.breakpoints.up('md')]: { padding: theme.spacing(4), }, + '&:hover': { + transition: 'background-color 0.2s ease-in-out', + backgroundColor: theme.palette.neutral.light, + }, })); const StyledRow = styled('div')(() => ({ @@ -31,6 +37,14 @@ const StyledRow = styled('div')(() => ({ justifyContent: 'space-between', })); +const StyledTitleRow = styled(StyledRow)(() => ({ + alignItems: 'flex-start', +})); + +const StyledBottomRow = styled(StyledRow)(() => ({ + marginTop: 'auto', +})); + const StyledHeaderTitle = styled('h2')(({ theme }) => ({ fontSize: theme.fontSizes.mainHeader, fontWeight: theme.fontWeight.medium, @@ -55,7 +69,13 @@ const StyledCounterDescription = styled('span')(({ theme }) => ({ marginLeft: theme.spacing(1), })); -const ProjectBadgeContainer = styled('div')(() => ({})); +const ProjectBadgeContainer = styled('div')(() => ({ + maxWidth: '50%', +})); + +const StyledBadge = styled(Badge)(() => ({ + marginRight: 0.5, +})); interface IGroupCardProps { group: IGroup; @@ -63,12 +83,12 @@ interface IGroupCardProps { export const GroupCard = ({ group }: IGroupCardProps) => { const [removeOpen, setRemoveOpen] = useState(false); - + const navigate = useNavigate(); return ( <> - + {group.name} { onRemove={() => setRemoveOpen(true)} /> - + {group.description} - + 0} show={} @@ -92,13 +112,26 @@ export const GroupCard = ({ group }: IGroupCardProps) => { 0} show={group.projects.map(project => ( - } - sx={{ marginRight: 0.5 }} + - {project} - + { + e.preventDefault(); + navigate( + `/projects/${project}/access` + ); + }} + color="secondary" + icon={} + > + {project} + + ))} elseShow={ { } /> - + = ({ - Remove group + Delete group diff --git a/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCardAvatars/GroupCardAvatars.tsx b/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCardAvatars/GroupCardAvatars.tsx index 73e6245bd0..01933b912f 100644 --- a/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCardAvatars/GroupCardAvatars.tsx +++ b/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCardAvatars/GroupCardAvatars.tsx @@ -15,6 +15,9 @@ const StyledAvatars = styled('div')(({ theme }) => ({ const StyledAvatar = styled(UserAvatar)(({ theme }) => ({ outline: `${theme.spacing(0.25)} solid ${theme.palette.background.paper}`, marginLeft: theme.spacing(-1), + '&:hover': { + outlineColor: theme.palette.primary.main, + }, })); interface IGroupCardAvatarsProps { @@ -44,6 +47,7 @@ export const GroupCardAvatars = ({ users }: IGroupCardAvatarsProps) => { {shownUsers.map(user => ( { diff --git a/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCardAvatars/GroupPopover/GroupPopover.tsx b/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCardAvatars/GroupPopover/GroupPopover.tsx index 5e59c2e9a5..665ee8bb74 100644 --- a/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCardAvatars/GroupPopover/GroupPopover.tsx +++ b/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCardAvatars/GroupPopover/GroupPopover.tsx @@ -1,21 +1,18 @@ -import { Badge, Popover, styled } from '@mui/material'; +import { Popover, styled } from '@mui/material'; import { IGroupUser, Role } from 'interfaces/group'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { Badge as StyledBadge } from 'component/common/Badge/Badge'; -import StarIcon from '@mui/icons-material/Star'; +import { Badge } from 'component/common/Badge/Badge'; +import { StarRounded } from '@mui/icons-material'; const StyledPopover = styled(Popover)(({ theme }) => ({ pointerEvents: 'none', '.MuiPaper-root': { - padding: '12px', + padding: theme.spacing(2), }, })); -const StyledPopupStar = styled(StarIcon)(({ theme }) => ({ +const StyledPopupStar = styled(StarRounded)(({ theme }) => ({ color: theme.palette.warning.main, - fontSize: theme.fontSizes.smallBody, - marginLeft: theme.spacing(0.1), - marginTop: theme.spacing(2), })); const StyledName = styled('div')(({ theme }) => ({ @@ -55,22 +52,10 @@ export const GroupPopover = ({ > {user?.role}} + show={{user?.role}} elseShow={ - } - > - - {user?.role} - + }> + {user?.role} } /> diff --git a/frontend/src/component/admin/groups/GroupsList/GroupEmpty/GroupEmpty.tsx b/frontend/src/component/admin/groups/GroupsList/GroupEmpty/GroupEmpty.tsx new file mode 100644 index 0000000000..768127474d --- /dev/null +++ b/frontend/src/component/admin/groups/GroupsList/GroupEmpty/GroupEmpty.tsx @@ -0,0 +1,35 @@ +import { Button, styled, Typography } from '@mui/material'; +import { Link } from 'react-router-dom'; + +export const GroupEmpty = () => { + const StyledContainerDiv = styled('div')(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + margin: theme.spacing(6), + marginLeft: 'auto', + marginRight: 'auto', + })); + + const StyledTitle = styled(Typography)(({ theme }) => ({ + fontSize: theme.fontSizes.bodySize, + marginBottom: theme.spacing(2.5), + })); + + return ( + + + No groups available. Get started by adding a new group. + + + + ); +}; diff --git a/frontend/src/component/admin/groups/GroupsList/GroupsList.tsx b/frontend/src/component/admin/groups/GroupsList/GroupsList.tsx index 8f61209922..18d1756406 100644 --- a/frontend/src/component/admin/groups/GroupsList/GroupsList.tsx +++ b/frontend/src/component/admin/groups/GroupsList/GroupsList.tsx @@ -11,6 +11,7 @@ import theme from 'themes/theme'; import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; import { TablePlaceholder } from 'component/common/Table'; import { GroupCard } from './GroupCard/GroupCard'; +import { GroupEmpty } from './GroupEmpty/GroupEmpty'; type PageQueryType = Partial>; @@ -123,12 +124,7 @@ export const GroupsList: VFC = () => { ” } - elseShow={ - - No groups available. Get started by adding a new - group. - - } + elseShow={} /> } /> diff --git a/frontend/src/component/admin/groups/RemoveGroup/RemoveGroup.tsx b/frontend/src/component/admin/groups/RemoveGroup/RemoveGroup.tsx index ddf7d5a34d..3dfed93853 100644 --- a/frontend/src/component/admin/groups/RemoveGroup/RemoveGroup.tsx +++ b/frontend/src/component/admin/groups/RemoveGroup/RemoveGroup.tsx @@ -48,10 +48,10 @@ export const RemoveGroup: FC = ({ onClose={() => { setOpen(false); }} - title="Remove group" + title="Delete group" > - Are you sure you wish to remove {group.name}? + Are you sure you wish to delete {group.name}? If this group is currently assigned to one or more projects then users belonging to this group may lose access to those projects. diff --git a/frontend/src/component/common/Badge/Badge.tsx b/frontend/src/component/common/Badge/Badge.tsx index bbcce6fa69..b944542e25 100644 --- a/frontend/src/component/common/Badge/Badge.tsx +++ b/frontend/src/component/common/Badge/Badge.tsx @@ -1,5 +1,5 @@ import { styled, SxProps, Theme } from '@mui/material'; -import { +import React, { cloneElement, FC, ForwardedRef, @@ -17,6 +17,8 @@ interface IBadgeProps { className?: string; sx?: SxProps; children?: ReactNode; + title?: string; + onClick?: (event: React.SyntheticEvent) => void; } interface IBadgeIconProps { diff --git a/frontend/src/component/common/MainHeader/MainHeader.tsx b/frontend/src/component/common/MainHeader/MainHeader.tsx index 2711d6df12..2b46d0237b 100644 --- a/frontend/src/component/common/MainHeader/MainHeader.tsx +++ b/frontend/src/component/common/MainHeader/MainHeader.tsx @@ -1,6 +1,7 @@ import { Paper, styled } from '@mui/material'; import { usePageTitle } from 'hooks/usePageTitle'; import { ReactNode } from 'react'; +import { ConditionallyRender } from '../ConditionallyRender/ConditionallyRender'; const StyledMainHeader = styled(Paper)(({ theme }) => ({ borderRadius: theme.shape.borderRadiusLarge, @@ -49,7 +50,15 @@ export const MainHeader = ({ {title} {actions} - Description:{description} + + Description: + {description} + + } + /> ); }; diff --git a/frontend/src/component/common/UserAvatar/UserAvatar.tsx b/frontend/src/component/common/UserAvatar/UserAvatar.tsx index 523cdbab1f..c2ec2288ae 100644 --- a/frontend/src/component/common/UserAvatar/UserAvatar.tsx +++ b/frontend/src/component/common/UserAvatar/UserAvatar.tsx @@ -9,11 +9,11 @@ import { import { IUser } from 'interfaces/user'; import { FC } from 'react'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import StarIcon from '@mui/icons-material/Star'; +import { StarRounded } from '@mui/icons-material'; const StyledAvatar = styled(Avatar)(({ theme }) => ({ - width: theme.spacing(4), - height: theme.spacing(4), + width: theme.spacing(3.5), + height: theme.spacing(3.5), margin: 'auto', backgroundColor: theme.palette.secondary.light, color: theme.palette.text.primary, @@ -21,7 +21,7 @@ const StyledAvatar = styled(Avatar)(({ theme }) => ({ fontWeight: theme.fontWeight.bold, })); -const StyledStar = styled(StarIcon)(({ theme }) => ({ +const StyledStar = styled(StarRounded)(({ theme }) => ({ color: theme.palette.warning.main, backgroundColor: theme.palette.background.paper, borderRadius: theme.shape.borderRadiusExtraLarge, diff --git a/frontend/src/component/project/ProjectAccess/ProjectAccessAssign/ProjectRoleDescription/ProjectRoleDescription.tsx b/frontend/src/component/project/ProjectAccess/ProjectAccessAssign/ProjectRoleDescription/ProjectRoleDescription.tsx index d4f66881d8..f9d2276693 100644 --- a/frontend/src/component/project/ProjectAccess/ProjectAccessAssign/ProjectRoleDescription/ProjectRoleDescription.tsx +++ b/frontend/src/component/project/ProjectAccess/ProjectAccessAssign/ProjectRoleDescription/ProjectRoleDescription.tsx @@ -44,27 +44,44 @@ export const ProjectRoleDescription: VFC = ({ const environments = useMemo(() => { const environments = new Set(); role.permissions - ?.filter((permission: any) => permission.environment !== '') + ?.filter((permission: any) => permission.environment) .forEach((permission: any) => { environments.add(permission.environment); }); return [...environments].sort(); }, [role]); + const projectPermissions = useMemo(() => { + return role.permissions?.filter( + (permission: any) => !permission.environment + ); + }, [role]); + return ( - - Project permissions - - - {role.permissions - ?.filter((permission: any) => permission.environment === '') - .map((permission: any) => permission.displayName) - .sort() - .map((permission: any) => ( -

{permission}

- ))} -
+ + + Project permissions + + + {role.permissions + ?.filter( + (permission: any) => !permission.environment + ) + .map( + (permission: any) => permission.displayName + ) + .sort() + .map((permission: any) => ( +

{permission}

+ ))} +
+ + } + />