diff --git a/frontend/src/component/admin/groups/Group/Group.tsx b/frontend/src/component/admin/groups/Group/Group.tsx
index a0a35cc620..f50bd58b92 100644
--- a/frontend/src/component/admin/groups/Group/Group.tsx
+++ b/frontend/src/component/admin/groups/Group/Group.tsx
@@ -1,6 +1,5 @@
import { useEffect, useMemo, useState, VFC } from 'react';
import {
- Avatar,
Button,
IconButton,
styled,
@@ -37,12 +36,7 @@ import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell';
import { AddGroupUser } from './AddGroupUser/AddGroupUser';
import { EditGroupUser } from './EditGroupUser/EditGroupUser';
import { RemoveGroupUser } from './RemoveGroupUser/RemoveGroupUser';
-
-const StyledAvatar = styled(Avatar)(({ theme }) => ({
- width: theme.spacing(4),
- height: theme.spacing(4),
- margin: 'auto',
-}));
+import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
const StyledEdit = styled(Edit)(({ theme }) => ({
fontSize: theme.fontSizes.mainHeader,
@@ -87,14 +81,7 @@ export const Group: VFC = () => {
accessor: 'imageUrl',
Cell: ({ row: { original: user } }: any) => (
-
+
),
maxWidth: 85,
diff --git a/frontend/src/component/admin/groups/GroupForm/GroupFormUsersTable/GroupFormUsersTable.tsx b/frontend/src/component/admin/groups/GroupForm/GroupFormUsersTable/GroupFormUsersTable.tsx
index 46cc82d0f0..abbfc907e5 100644
--- a/frontend/src/component/admin/groups/GroupForm/GroupFormUsersTable/GroupFormUsersTable.tsx
+++ b/frontend/src/component/admin/groups/GroupForm/GroupFormUsersTable/GroupFormUsersTable.tsx
@@ -1,5 +1,5 @@
import { useMemo, VFC } from 'react';
-import { Avatar, IconButton, styled, Tooltip } from '@mui/material';
+import { IconButton, Tooltip } from '@mui/material';
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
import { IGroupUser } from 'interfaces/group';
import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell';
@@ -10,12 +10,7 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
import { VirtualizedTable } from 'component/common/Table';
import { useFlexLayout, useSortBy, useTable } from 'react-table';
import { sortTypes } from 'utils/sortTypes';
-
-const StyledAvatar = styled(Avatar)(({ theme }) => ({
- width: theme.spacing(4),
- height: theme.spacing(4),
- margin: 'auto',
-}));
+import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
interface IGroupFormUsersTableProps {
users: IGroupUser[];
@@ -33,14 +28,7 @@ export const GroupFormUsersTable: VFC = ({
accessor: 'imageUrl',
Cell: ({ row: { original: user } }: any) => (
-
+
),
maxWidth: 85,
diff --git a/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCard.tsx b/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCard.tsx
index a41e22a736..2f78c3e6bc 100644
--- a/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCard.tsx
+++ b/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCard.tsx
@@ -7,6 +7,7 @@ import { Badge } from 'component/common/Badge/Badge';
import { GroupCardActions } from './GroupCardActions/GroupCardActions';
import { RemoveGroup } from 'component/admin/groups/RemoveGroup/RemoveGroup';
import { useState } from 'react';
+import TopicOutlinedIcon from '@mui/icons-material/TopicOutlined';
const StyledLink = styled(Link)(({ theme }) => ({
textDecoration: 'none',
@@ -93,6 +94,7 @@ export const GroupCard = ({ group }: IGroupCardProps) => {
show={group.projects.map(project => (
}
sx={{ marginRight: 0.5 }}
>
{project}
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 c5daf720a8..73e6245bd0 100644
--- a/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCardAvatars/GroupCardAvatars.tsx
+++ b/frontend/src/component/admin/groups/GroupsList/GroupCard/GroupCardAvatars/GroupCardAvatars.tsx
@@ -1,9 +1,9 @@
-import { Avatar, Badge, styled } from '@mui/material';
+import { styled } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { IGroupUser, Role } from 'interfaces/group';
import React, { useMemo, useState } from 'react';
-import StarIcon from '@mui/icons-material/Star';
import { GroupPopover } from './GroupPopover/GroupPopover';
+import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
const StyledAvatars = styled('div')(({ theme }) => ({
display: 'inline-flex',
@@ -12,25 +12,8 @@ const StyledAvatars = styled('div')(({ theme }) => ({
marginLeft: theme.spacing(1),
}));
-const StyledAvatar = styled(Avatar)(({ theme }) => ({
- width: theme.spacing(4),
- height: theme.spacing(4),
- outline: `2px solid ${theme.palette.background.paper}`,
- marginLeft: theme.spacing(-1),
-}));
-
-const StyledAvatarMore = styled(StyledAvatar)(({ theme }) => ({
- backgroundColor: theme.palette.secondary.light,
- color: theme.palette.text.primary,
- fontSize: theme.fontSizes.smallerBody,
- fontWeight: theme.fontWeight.bold,
-}));
-
-const StyledStar = styled(StarIcon)(({ theme }) => ({
- color: theme.palette.warning.main,
- backgroundColor: theme.palette.background.paper,
- borderRadius: theme.shape.borderRadiusExtraLarge,
- fontSize: theme.fontSizes.smallBody,
+const StyledAvatar = styled(UserAvatar)(({ theme }) => ({
+ outline: `${theme.spacing(0.25)} solid ${theme.palette.background.paper}`,
marginLeft: theme.spacing(-1),
}));
@@ -60,50 +43,22 @@ export const GroupCardAvatars = ({ users }: IGroupCardAvatarsProps) => {
return (
{shownUsers.map(user => (
- {
- onPopoverOpen(event);
- setPopupUser(user);
- }}
- onMouseLeave={onPopoverClose}
- />
- }
- elseShow={
- }
- >
- {
- onPopoverOpen(event);
- setPopupUser(user);
- }}
- onMouseLeave={onPopoverClose}
- />
-
- }
+ {
+ onPopoverOpen(event);
+ setPopupUser(user);
+ }}
+ onMouseLeave={onPopoverClose}
/>
))}
9}
show={
-
+
+{users.length - shownUsers.length}
-
+
}
/>
- {user?.name}
+ {user?.name || user?.username}
{user?.email}
);
diff --git a/frontend/src/component/admin/users/UsersList/ChangePassword/ChangePassword.tsx b/frontend/src/component/admin/users/UsersList/ChangePassword/ChangePassword.tsx
index 6521dd27c3..251e9f7d43 100644
--- a/frontend/src/component/admin/users/UsersList/ChangePassword/ChangePassword.tsx
+++ b/frontend/src/component/admin/users/UsersList/ChangePassword/ChangePassword.tsx
@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import classnames from 'classnames';
-import { Avatar, TextField, Typography } from '@mui/material';
+import { styled, TextField, Typography } from '@mui/material';
import { trim } from 'component/common/util';
import { modalStyles } from 'component/admin/users/util';
import { Dialogue } from 'component/common/Dialogue/Dialogue';
@@ -11,6 +11,13 @@ import { useThemeStyles } from 'themes/themeStyles';
import PasswordMatcher from 'component/user/common/ResetPasswordForm/PasswordMatcher/PasswordMatcher';
import { IUser } from 'interfaces/user';
import useAdminUsersApi from 'hooks/api/actions/useAdminUsersApi/useAdminUsersApi';
+import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
+
+const StyledUserAvatar = styled(UserAvatar)(({ theme }) => ({
+ width: theme.spacing(5),
+ height: theme.spacing(5),
+ margin: 0,
+}));
interface IChangePasswordProps {
showDialog: boolean;
@@ -85,14 +92,7 @@ const ChangePassword = ({
Changing password for user
-
+
({
+ width: theme.spacing(5),
+ height: theme.spacing(5),
+ margin: 0,
+}));
interface IDeleteUserProps {
showDialog: boolean;
@@ -51,14 +58,7 @@ const DeleteUser = ({
}
/>
-
+
({
- width: theme.spacing(4),
- height: theme.spacing(4),
- margin: 'auto',
-}));
+import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
const UsersList = () => {
const navigate = useNavigate();
@@ -125,14 +119,7 @@ const UsersList = () => {
accessor: 'imageUrl',
Cell: ({ row: { original: user } }: any) => (
-
+
),
disableGlobalFilter: true,
@@ -212,7 +199,7 @@ const UsersList = () => {
setHiddenColumns,
} = useTable(
{
- columns: columns as any[], // TODO: fix after `react-table` v8 update
+ columns: columns,
data,
initialState,
sortTypes,
diff --git a/frontend/src/component/common/Badge/Badge.tsx b/frontend/src/component/common/Badge/Badge.tsx
index 855508bd5d..bbcce6fa69 100644
--- a/frontend/src/component/common/Badge/Badge.tsx
+++ b/frontend/src/component/common/Badge/Badge.tsx
@@ -24,12 +24,14 @@ interface IBadgeIconProps {
}
const StyledBadge = styled('div')(
- ({ theme, color = 'neutral' }) => ({
+ ({ theme, color = 'neutral', icon }) => ({
display: 'inline-flex',
alignItems: 'center',
- padding: theme.spacing(0.5, 1),
+ padding: theme.spacing(icon ? 0.375 : 0.625, 1),
borderRadius: theme.shape.borderRadius,
fontSize: theme.fontSizes.smallerBody,
+ fontWeight: theme.fontWeight.bold,
+ lineHeight: 1,
backgroundColor: theme.palette[color].light,
color: theme.palette[color].dark,
border: `1px solid ${theme.palette[color].border}`,
@@ -58,6 +60,7 @@ export const Badge: FC = forwardRef(
) => (
({
+ width: theme.spacing(4),
+ height: theme.spacing(4),
+ margin: 'auto',
+ backgroundColor: theme.palette.secondary.light,
+ color: theme.palette.text.primary,
+ fontSize: theme.fontSizes.smallerBody,
+ fontWeight: theme.fontWeight.bold,
+}));
+
+const StyledStar = styled(StarIcon)(({ theme }) => ({
+ color: theme.palette.warning.main,
+ backgroundColor: theme.palette.background.paper,
+ borderRadius: theme.shape.borderRadiusExtraLarge,
+ fontSize: theme.fontSizes.smallBody,
+ marginLeft: theme.spacing(-1),
+}));
+
+interface IUserAvatarProps extends AvatarProps {
+ user?: IUser;
+ star?: boolean;
+ src?: string;
+ title?: string;
+ onMouseEnter?: (event: any) => void;
+ onMouseLeave?: () => void;
+ className?: string;
+ sx?: SxProps;
+}
+
+export const UserAvatar: FC = ({
+ user,
+ star,
+ src,
+ title,
+ onMouseEnter,
+ onMouseLeave,
+ className,
+ sx,
+ children,
+ ...props
+}) => {
+ if (!title && !onMouseEnter && user) {
+ title = `${user?.name || user?.email || user?.username} (id: ${
+ user?.id
+ })`;
+ }
+
+ if (!src && user) {
+ src = user?.imageUrl;
+ }
+
+ let fallback;
+ if (!children && user) {
+ fallback = user?.name || user?.email || user?.username;
+ if (fallback && fallback.includes(' ')) {
+ fallback = `${fallback.split(' ')[0][0]}${
+ fallback.split(' ')[1][0]
+ }`.toUpperCase();
+ } else if (fallback) {
+ fallback = fallback[0].toUpperCase();
+ }
+ }
+
+ const avatar = (
+
+
+
+ );
+
+ return (
+ }
+ >
+ {avatar}
+
+ }
+ elseShow={avatar}
+ />
+ );
+};
diff --git a/frontend/src/component/project/ProjectAccess/ProjectAccessTable/ProjectAccessTable.tsx b/frontend/src/component/project/ProjectAccess/ProjectAccessTable/ProjectAccessTable.tsx
index 6fc76c2828..f6c4b01535 100644
--- a/frontend/src/component/project/ProjectAccess/ProjectAccessTable/ProjectAccessTable.tsx
+++ b/frontend/src/component/project/ProjectAccess/ProjectAccessTable/ProjectAccessTable.tsx
@@ -1,7 +1,7 @@
import { useEffect, useMemo, useState, VFC } from 'react';
import { SortingRule, useFlexLayout, useSortBy, useTable } from 'react-table';
import { VirtualizedTable, TablePlaceholder } from 'component/common/Table';
-import { Avatar, Button, styled, useMediaQuery, useTheme } from '@mui/material';
+import { Button, useMediaQuery, useTheme } from '@mui/material';
import { Delete, Edit } from '@mui/icons-material';
import { sortTypes } from 'utils/sortTypes';
import useProjectAccess, {
@@ -32,16 +32,7 @@ import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { IUser } from 'interfaces/user';
import { IGroup } from 'interfaces/group';
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
-
-const StyledAvatar = styled(Avatar)(({ theme }) => ({
- width: theme.spacing(4),
- height: theme.spacing(4),
- margin: 'auto',
- backgroundColor: theme.palette.secondary.light,
- color: theme.palette.text.primary,
- fontSize: theme.fontSizes.smallBody,
- fontWeight: theme.fontWeight.bold,
-}));
+import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
export type PageQueryType = Partial<
Record<'sort' | 'order' | 'search', string>
@@ -106,18 +97,9 @@ export const ProjectAccessTable: VFC = () => {
accessor: 'imageUrl',
Cell: ({ row: { original: row } }: any) => (
-
+
{row.entity.users?.length}
-
+
),
maxWidth: 85,
diff --git a/frontend/src/component/project/ProjectAccess/ProjectGroupView/ProjectGroupView.tsx b/frontend/src/component/project/ProjectAccess/ProjectGroupView/ProjectGroupView.tsx
index 7fab7cfa8b..44a9afbe9e 100644
--- a/frontend/src/component/project/ProjectAccess/ProjectGroupView/ProjectGroupView.tsx
+++ b/frontend/src/component/project/ProjectAccess/ProjectGroupView/ProjectGroupView.tsx
@@ -1,5 +1,5 @@
import { Delete, Edit } from '@mui/icons-material';
-import { Avatar, styled, useMediaQuery, useTheme } from '@mui/material';
+import { styled, useMediaQuery, useTheme } from '@mui/material';
import { GroupUserRoleCell } from 'component/admin/groups/GroupUserRoleCell/GroupUserRoleCell';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { PageContent } from 'component/common/PageContent/PageContent';
@@ -13,6 +13,7 @@ import { HighlightCell } from 'component/common/Table/cells/HighlightCell/Highli
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
import { TimeAgoCell } from 'component/common/Table/cells/TimeAgoCell/TimeAgoCell';
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
+import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions';
import { useSearch } from 'hooks/useSearch';
import { IGroup, IGroupUser } from 'interfaces/group';
@@ -20,12 +21,6 @@ import { VFC, useState } from 'react';
import { SortingRule, useFlexLayout, useSortBy, useTable } from 'react-table';
import { sortTypes } from 'utils/sortTypes';
-const StyledAvatar = styled(Avatar)(({ theme }) => ({
- width: theme.spacing(4),
- height: theme.spacing(4),
- margin: 'auto',
-}));
-
const StyledPageContent = styled(PageContent)(({ theme }) => ({
height: '100vh',
padding: theme.spacing(7.5, 6),
@@ -54,14 +49,7 @@ const columns = [
accessor: 'imageUrl',
Cell: ({ row: { original: user } }: any) => (
-
+
),
maxWidth: 85,