1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-15 01:16:22 +02:00

refactor: create user avatar component, clean up (#1151)

* refactor: create user avatar component, clean up

* fix: small changes regarding the new badge component
This commit is contained in:
Nuno Góis 2022-07-26 18:50:19 +01:00 committed by GitHub
parent f86bd16c7e
commit 61c0d6f0a1
12 changed files with 172 additions and 166 deletions

View File

@ -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) => (
<TextCell>
<StyledAvatar
data-loading
alt="Gravatar"
src={user.imageUrl}
title={`${
user.name || user.email || user.username
} (id: ${user.id})`}
/>
<UserAvatar user={user} />
</TextCell>
),
maxWidth: 85,

View File

@ -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<IGroupFormUsersTableProps> = ({
accessor: 'imageUrl',
Cell: ({ row: { original: user } }: any) => (
<TextCell>
<StyledAvatar
data-loading
alt="Gravatar"
src={user.imageUrl}
title={`${
user.name || user.email || user.username
} (id: ${user.id})`}
/>
<UserAvatar user={user} />
</TextCell>
),
maxWidth: 85,

View File

@ -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 => (
<Badge
color="secondary"
icon={<TopicOutlinedIcon />}
sx={{ marginRight: 0.5 }}
>
{project}

View File

@ -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 (
<StyledAvatars>
{shownUsers.map(user => (
<ConditionallyRender
key={user.id}
condition={user.role === Role.Member}
show={
<StyledAvatar
data-loading
alt="Gravatar"
src={user.imageUrl}
onMouseEnter={event => {
onPopoverOpen(event);
setPopupUser(user);
}}
onMouseLeave={onPopoverClose}
/>
}
elseShow={
<Badge
overlap="circular"
anchorOrigin={{
vertical: 'top',
horizontal: 'left',
}}
badgeContent={<StyledStar />}
>
<StyledAvatar
data-loading
alt="Gravatar"
src={user.imageUrl}
onMouseEnter={event => {
onPopoverOpen(event);
setPopupUser(user);
}}
onMouseLeave={onPopoverClose}
/>
</Badge>
}
<StyledAvatar
user={user}
star={user.role === Role.Owner}
onMouseEnter={event => {
onPopoverOpen(event);
setPopupUser(user);
}}
onMouseLeave={onPopoverClose}
/>
))}
<ConditionallyRender
condition={users.length > 9}
show={
<StyledAvatarMore>
<StyledAvatar>
+{users.length - shownUsers.length}
</StyledAvatarMore>
</StyledAvatar>
}
/>
<GroupPopover

View File

@ -1,7 +1,6 @@
import { Badge, 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';
@ -76,7 +75,7 @@ export const GroupPopover = ({
}
/>
<StyledName>{user?.name}</StyledName>
<StyledName>{user?.name || user?.username}</StyledName>
<div>{user?.email}</div>
</StyledPopover>
);

View File

@ -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
</Typography>
<div className={themeStyles.flexRow}>
<Avatar
variant="rounded"
alt="Gravatar"
src={user.imageUrl}
title={`${
user.name || user.email || user.username
} (id: ${user.id})`}
/>
<StyledUserAvatar user={user} variant="rounded" />
<Typography
variant="subtitle1"
style={{ marginLeft: '1rem' }}

View File

@ -2,11 +2,18 @@ import React from 'react';
import { Dialogue } from 'component/common/Dialogue/Dialogue';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { REMOVE_USER_ERROR } from 'hooks/api/actions/useAdminUsersApi/useAdminUsersApi';
import { Alert } from '@mui/material';
import { Alert, styled } from '@mui/material';
import useLoading from 'hooks/useLoading';
import { Avatar, Typography } from '@mui/material';
import { Typography } from '@mui/material';
import { useThemeStyles } from 'themes/themeStyles';
import { IUser } from 'interfaces/user';
import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
const StyledUserAvatar = styled(UserAvatar)(({ theme }) => ({
width: theme.spacing(5),
height: theme.spacing(5),
margin: 0,
}));
interface IDeleteUserProps {
showDialog: boolean;
@ -51,14 +58,7 @@ const DeleteUser = ({
}
/>
<div data-loading className={themeStyles.flexRow}>
<Avatar
variant="rounded"
alt="Gravatar"
src={user.imageUrl}
title={`${
user.name || user.email || user.username
} (id: ${user.id})`}
/>
<StyledUserAvatar user={user} variant="rounded" />
<Typography
variant="subtitle1"
style={{ marginLeft: '1rem' }}

View File

@ -1,4 +1,3 @@
/* eslint-disable no-alert */
import React, { useState, useEffect, useMemo } from 'react';
import {
Table,
@ -21,7 +20,7 @@ import { formatUnknownError } from 'utils/formatUnknownError';
import { useUsersPlan } from 'hooks/useUsersPlan';
import { PageContent } from 'component/common/PageContent/PageContent';
import { PageHeader } from 'component/common/PageHeader/PageHeader';
import { Avatar, Button, styled, useMediaQuery } from '@mui/material';
import { Button, useMediaQuery } from '@mui/material';
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
import { UserTypeCell } from './UserTypeCell/UserTypeCell';
import { useGlobalFilter, useSortBy, useTable } from 'react-table';
@ -34,12 +33,7 @@ import theme from 'themes/theme';
import { TimeAgoCell } from 'component/common/Table/cells/TimeAgoCell/TimeAgoCell';
import { UsersActionsCell } from './UsersActionsCell/UsersActionsCell';
import { Search } from 'component/common/Search/Search';
const StyledAvatar = styled(Avatar)(({ theme }) => ({
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) => (
<TextCell>
<StyledAvatar
data-loading
alt="Gravatar"
src={user.imageUrl}
title={`${
user.name || user.email || user.username
} (id: ${user.id})`}
/>
<UserAvatar user={user} />
</TextCell>
),
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,

View File

@ -24,12 +24,14 @@ interface IBadgeIconProps {
}
const StyledBadge = styled('div')<IBadgeProps>(
({ 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<IBadgeProps> = forwardRef(
) => (
<StyledBadge
color={color}
icon={icon}
className={className}
sx={sx}
{...props}

View File

@ -0,0 +1,115 @@
import {
Avatar,
AvatarProps,
Badge,
styled,
SxProps,
Theme,
} from '@mui/material';
import { IUser } from 'interfaces/user';
import { FC } from 'react';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import StarIcon from '@mui/icons-material/Star';
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.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<Theme>;
}
export const UserAvatar: FC<IUserAvatarProps> = ({
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 = (
<StyledAvatar
className={className}
sx={sx}
{...props}
data-loading
alt="Gravatar"
src={src}
title={title}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
<ConditionallyRender
condition={Boolean(fallback)}
show={fallback}
elseShow={children}
/>
</StyledAvatar>
);
return (
<ConditionallyRender
condition={Boolean(star)}
show={
<Badge
overlap="circular"
anchorOrigin={{
vertical: 'top',
horizontal: 'left',
}}
badgeContent={<StyledStar />}
>
{avatar}
</Badge>
}
elseShow={avatar}
/>
);
};

View File

@ -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) => (
<TextCell>
<StyledAvatar
data-loading
alt="Gravatar"
src={row.entity.imageUrl}
title={`${
row.entity.name ||
row.entity.email ||
row.entity.username
} (id: ${row.entity.id})`}
>
<UserAvatar user={row.entity}>
{row.entity.users?.length}
</StyledAvatar>
</UserAvatar>
</TextCell>
),
maxWidth: 85,

View File

@ -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) => (
<TextCell>
<StyledAvatar
data-loading
alt="Gravatar"
src={user.imageUrl}
title={`${user.name || user.email || user.username} (id: ${
user.id
})`}
/>
<UserAvatar user={user} />
</TextCell>
),
maxWidth: 85,