mirror of
https://github.com/Unleash/unleash.git
synced 2024-10-28 19:06:12 +01:00
feat: new project card (#7992)
This commit is contained in:
parent
3188f991f4
commit
8923e28d83
@ -2,15 +2,10 @@ import type { FC } from 'react';
|
||||
import useToast from 'hooks/useToast';
|
||||
import { useFavoriteProjectsApi } from 'hooks/api/actions/useFavoriteProjectsApi/useFavoriteProjectsApi';
|
||||
import useProjects from 'hooks/api/getters/useProjects/useProjects';
|
||||
import { styled } from '@mui/material';
|
||||
import { FavoriteIconButton } from 'component/common/FavoriteIconButton/FavoriteIconButton';
|
||||
|
||||
type FavoriteActionProps = { id: string; isFavorite?: boolean };
|
||||
|
||||
const StyledFavoriteIconButton = styled(FavoriteIconButton)(({ theme }) => ({
|
||||
margin: theme.spacing(1, 2, 0, 0),
|
||||
}));
|
||||
|
||||
export const FavoriteAction: FC<FavoriteActionProps> = ({ id, isFavorite }) => {
|
||||
const { setToastApiError } = useToast();
|
||||
const { favorite, unfavorite } = useFavoriteProjectsApi();
|
||||
@ -31,7 +26,7 @@ export const FavoriteAction: FC<FavoriteActionProps> = ({ id, isFavorite }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledFavoriteIconButton
|
||||
<FavoriteIconButton
|
||||
onClick={onFavorite}
|
||||
isFavorite={Boolean(isFavorite)}
|
||||
size='medium'
|
@ -12,21 +12,10 @@ import {
|
||||
} from './ProjectCard.styles';
|
||||
import { ProjectCardFooter } from './ProjectCardFooter/ProjectCardFooter';
|
||||
import { ProjectModeBadge } from './ProjectModeBadge/ProjectModeBadge';
|
||||
import type { ProjectSchemaOwners } from 'openapi';
|
||||
import { ProjectIcon } from 'component/common/ProjectIcon/ProjectIcon';
|
||||
import { FavoriteAction } from './ProjectCardFooter/FavoriteAction/FavoriteAction';
|
||||
|
||||
interface IProjectCardProps {
|
||||
name: string;
|
||||
featureCount: number;
|
||||
health: number;
|
||||
memberCount?: number;
|
||||
id: string;
|
||||
onHover: () => void;
|
||||
favorite?: boolean;
|
||||
mode: string;
|
||||
owners?: ProjectSchemaOwners;
|
||||
}
|
||||
import { FavoriteAction } from './FavoriteAction/FavoriteAction';
|
||||
import type { IProjectCard } from 'interfaces/project';
|
||||
import { Box } from '@mui/material';
|
||||
|
||||
export const ProjectCard = ({
|
||||
name,
|
||||
@ -38,7 +27,7 @@ export const ProjectCard = ({
|
||||
mode,
|
||||
favorite = false,
|
||||
owners,
|
||||
}: IProjectCardProps) => (
|
||||
}: IProjectCard) => (
|
||||
<StyledProjectCard onMouseEnter={onHover}>
|
||||
<StyledProjectCardBody>
|
||||
<StyledDivHeader>
|
||||
@ -79,7 +68,9 @@ export const ProjectCard = ({
|
||||
</StyledDivInfo>
|
||||
</StyledProjectCardBody>
|
||||
<ProjectCardFooter id={id} owners={owners}>
|
||||
<FavoriteAction id={id} isFavorite={favorite} />
|
||||
<Box sx={(theme) => ({ margin: theme.spacing(1, 2, 0, 0) })}>
|
||||
<FavoriteAction id={id} isFavorite={favorite} />
|
||||
</Box>
|
||||
</ProjectCardFooter>
|
||||
</StyledProjectCard>
|
||||
);
|
||||
|
@ -34,7 +34,6 @@ export type ProjectArchiveCardProps = {
|
||||
id: string;
|
||||
name: string;
|
||||
archivedAt?: string;
|
||||
archivedFeaturesCount?: number;
|
||||
onRevive: () => void;
|
||||
onDelete: () => void;
|
||||
mode?: string;
|
||||
@ -45,7 +44,6 @@ export const ProjectArchiveCard: FC<ProjectArchiveCardProps> = ({
|
||||
id,
|
||||
name,
|
||||
archivedAt,
|
||||
archivedFeaturesCount,
|
||||
onRevive,
|
||||
onDelete,
|
||||
mode,
|
||||
|
@ -41,20 +41,22 @@ export const StyledDivHeader = styled('div')(({ theme }) => ({
|
||||
gap: theme.spacing(1),
|
||||
}));
|
||||
|
||||
export const StyledCardTitle = styled('h3')(({ theme }) => ({
|
||||
fontWeight: theme.typography.fontWeightRegular,
|
||||
fontSize: theme.typography.body1.fontSize,
|
||||
lineClamp: '2',
|
||||
WebkitLineClamp: 2,
|
||||
lineHeight: '1.2',
|
||||
display: '-webkit-box',
|
||||
boxOrient: 'vertical',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
alignItems: 'flex-start',
|
||||
WebkitBoxOrient: 'vertical',
|
||||
wordBreak: 'break-word',
|
||||
}));
|
||||
export const StyledCardTitle = styled('h3')<{ lines?: number }>(
|
||||
({ theme, lines = 2 }) => ({
|
||||
fontWeight: theme.typography.fontWeightRegular,
|
||||
fontSize: theme.typography.body1.fontSize,
|
||||
lineClamp: `${lines}`,
|
||||
WebkitLineClamp: lines,
|
||||
lineHeight: '1.2',
|
||||
display: '-webkit-box',
|
||||
boxOrient: 'vertical',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
alignItems: 'flex-start',
|
||||
WebkitBoxOrient: 'vertical',
|
||||
wordBreak: 'break-word',
|
||||
}),
|
||||
);
|
||||
|
||||
export const StyledBox = styled(Box)(() => ({
|
||||
...flexRow,
|
||||
|
@ -1,32 +1,37 @@
|
||||
import { DEFAULT_PROJECT_ID } from 'hooks/api/getters/useDefaultProject/useDefaultProjectId';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import {
|
||||
StyledProjectCard,
|
||||
StyledDivHeader,
|
||||
StyledBox,
|
||||
StyledCardTitle,
|
||||
StyledDivInfo,
|
||||
StyledParagraphInfo,
|
||||
StyledProjectCardBody,
|
||||
StyledIconBox,
|
||||
} from './ProjectCard.styles';
|
||||
import { ProjectCardFooter } from './ProjectCardFooter/ProjectCardFooter';
|
||||
import { ProjectModeBadge } from './ProjectModeBadge/ProjectModeBadge';
|
||||
import type { ProjectSchemaOwners } from 'openapi';
|
||||
import { ProjectIcon } from 'component/common/ProjectIcon/ProjectIcon';
|
||||
import { FavoriteAction } from './ProjectCardFooter/FavoriteAction/FavoriteAction';
|
||||
import { FavoriteAction } from './FavoriteAction/FavoriteAction';
|
||||
import { Box, styled } from '@mui/material';
|
||||
import { flexColumn } from 'themes/themeStyles';
|
||||
import { TimeAgo } from 'component/common/TimeAgo/TimeAgo';
|
||||
import { ProjectLastSeen } from './ProjectLastSeen/ProjectLastSeen';
|
||||
import type { IProjectCard } from 'interfaces/project';
|
||||
|
||||
interface IProjectCardProps {
|
||||
name: string;
|
||||
featureCount: number;
|
||||
health: number;
|
||||
memberCount?: number;
|
||||
id: string;
|
||||
onHover: () => void;
|
||||
favorite?: boolean;
|
||||
mode: string;
|
||||
owners?: ProjectSchemaOwners;
|
||||
}
|
||||
const StyledUpdated = styled('span')(({ theme }) => ({
|
||||
color: theme.palette.text.secondary,
|
||||
fontSize: theme.fontSizes.smallerBody,
|
||||
}));
|
||||
|
||||
const StyledCount = styled('strong')(({ theme }) => ({
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
}));
|
||||
|
||||
const StyledInfo = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
marginTop: theme.spacing(1),
|
||||
fontSize: theme.fontSizes.smallerBody,
|
||||
alignItems: 'flex-end',
|
||||
}));
|
||||
|
||||
export const ProjectCard = ({
|
||||
name,
|
||||
@ -38,48 +43,50 @@ export const ProjectCard = ({
|
||||
mode,
|
||||
favorite = false,
|
||||
owners,
|
||||
}: IProjectCardProps) => (
|
||||
lastUpdatedAt,
|
||||
lastReportedFlagUsage,
|
||||
}: IProjectCard) => (
|
||||
<StyledProjectCard onMouseEnter={onHover}>
|
||||
<StyledProjectCardBody>
|
||||
<StyledDivHeader>
|
||||
<StyledIconBox>
|
||||
<ProjectIcon />
|
||||
</StyledIconBox>
|
||||
<StyledBox data-loading>
|
||||
<StyledCardTitle>{name}</StyledCardTitle>
|
||||
</StyledBox>
|
||||
<Box
|
||||
data-loading
|
||||
sx={(theme) => ({
|
||||
...flexColumn,
|
||||
margin: theme.spacing(1, 'auto', 1, 0),
|
||||
})}
|
||||
>
|
||||
<StyledCardTitle lines={1} sx={{ margin: 0 }}>
|
||||
{name}
|
||||
</StyledCardTitle>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(lastUpdatedAt)}
|
||||
show={
|
||||
<StyledUpdated>
|
||||
Updated <TimeAgo date={lastUpdatedAt} />
|
||||
</StyledUpdated>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
<ProjectModeBadge mode={mode} />
|
||||
<FavoriteAction id={id} isFavorite={favorite} />
|
||||
</StyledDivHeader>
|
||||
<StyledDivInfo>
|
||||
<StyledInfo>
|
||||
<div>
|
||||
<StyledParagraphInfo data-loading>
|
||||
{featureCount}
|
||||
</StyledParagraphInfo>
|
||||
<p data-loading>{featureCount === 1 ? 'flag' : 'flags'}</p>
|
||||
<div>
|
||||
<StyledCount>{featureCount}</StyledCount> flag
|
||||
{featureCount === 1 ? '' : 's'}
|
||||
</div>
|
||||
<div>
|
||||
<StyledCount>{health}%</StyledCount> health
|
||||
</div>
|
||||
</div>
|
||||
<ConditionallyRender
|
||||
condition={id !== DEFAULT_PROJECT_ID}
|
||||
show={
|
||||
<div>
|
||||
<StyledParagraphInfo data-loading>
|
||||
{memberCount}
|
||||
</StyledParagraphInfo>
|
||||
<p data-loading>
|
||||
{memberCount === 1 ? 'member' : 'members'}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<div>
|
||||
<StyledParagraphInfo data-loading>
|
||||
{health}%
|
||||
</StyledParagraphInfo>
|
||||
<p data-loading>healthy</p>
|
||||
</div>
|
||||
</StyledDivInfo>
|
||||
<ProjectLastSeen date={lastReportedFlagUsage} />
|
||||
</StyledInfo>
|
||||
</StyledProjectCardBody>
|
||||
<ProjectCardFooter id={id} owners={owners}>
|
||||
<FavoriteAction id={id} isFavorite={favorite} />
|
||||
</ProjectCardFooter>
|
||||
<ProjectCardFooter id={id} owners={owners} memberCount={memberCount} />
|
||||
</StyledProjectCard>
|
||||
);
|
||||
|
@ -3,15 +3,21 @@ import type { FC } from 'react';
|
||||
import { Box, styled } from '@mui/material';
|
||||
import {
|
||||
type IProjectOwnersProps,
|
||||
ProjectOwners,
|
||||
} from '../ProjectOwners/ProjectOwners';
|
||||
ProjectOwners as LegacyProjectOwners,
|
||||
} from '../LegacyProjectOwners/LegacyProjectOwners';
|
||||
import { ProjectOwners } from './ProjectOwners/ProjectOwners';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { ProjectMembers } from './ProjectMembers/ProjectMembers';
|
||||
import { DEFAULT_PROJECT_ID } from 'hooks/api/getters/useDefaultProject/useDefaultProjectId';
|
||||
|
||||
interface IProjectCardFooterProps {
|
||||
id: string;
|
||||
id?: string;
|
||||
isFavorite?: boolean;
|
||||
children?: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
owners: IProjectOwnersProps['owners'];
|
||||
memberCount?: number;
|
||||
}
|
||||
|
||||
const StyledFooter = styled(Box)<{ disabled: boolean }>(
|
||||
@ -28,14 +34,29 @@ const StyledFooter = styled(Box)<{ disabled: boolean }>(
|
||||
);
|
||||
|
||||
export const ProjectCardFooter: FC<IProjectCardFooterProps> = ({
|
||||
id,
|
||||
children,
|
||||
owners,
|
||||
disabled = false,
|
||||
memberCount,
|
||||
}) => {
|
||||
const projectListImprovementsEnabled = useUiFlag('projectListImprovements');
|
||||
|
||||
return (
|
||||
<StyledFooter disabled={disabled}>
|
||||
<ProjectOwners owners={owners} />
|
||||
{children}
|
||||
<ConditionallyRender
|
||||
condition={Boolean(projectListImprovementsEnabled)}
|
||||
show={<ProjectOwners owners={owners} />}
|
||||
elseShow={<LegacyProjectOwners owners={owners} />}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
Boolean(projectListImprovementsEnabled) &&
|
||||
id !== DEFAULT_PROJECT_ID
|
||||
}
|
||||
show={<ProjectMembers count={memberCount} members={[]} />}
|
||||
elseShow={children}
|
||||
/>
|
||||
</StyledFooter>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,55 @@
|
||||
import type { FC } from 'react';
|
||||
import { styled } from '@mui/material';
|
||||
import { AvatarGroup } from 'component/common/AvatarGroup/AvatarGroup';
|
||||
import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
|
||||
import { flexColumn } from 'themes/themeStyles';
|
||||
|
||||
type ProjectMembersProps = {
|
||||
count?: number;
|
||||
members: Array<{
|
||||
imageUrl?: string;
|
||||
email?: string;
|
||||
name: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
const StyledContainer = styled('div')(({ theme }) => ({
|
||||
...flexColumn,
|
||||
alignItems: 'flex-end',
|
||||
padding: theme.spacing(0, 2, 0, 1),
|
||||
minWidth: 95,
|
||||
}));
|
||||
|
||||
const StyledDescription = styled('span')(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.smallerBody,
|
||||
color: theme.palette.text.secondary,
|
||||
textWrap: 'nowrap',
|
||||
}));
|
||||
|
||||
const StyledAvatar = styled(UserAvatar)(({ theme }) => ({
|
||||
width: theme.spacing(2),
|
||||
height: theme.spacing(2),
|
||||
}));
|
||||
|
||||
const AvatarComponent = ({ ...props }) => (
|
||||
<StyledAvatar {...props} disableTooltip />
|
||||
);
|
||||
|
||||
export const ProjectMembers: FC<ProjectMembersProps> = ({
|
||||
count = 0,
|
||||
members,
|
||||
}) => {
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledDescription data-loading>
|
||||
{count} member
|
||||
{count === 1 ? '' : 's'}
|
||||
</StyledDescription>
|
||||
<AvatarGroup
|
||||
users={members}
|
||||
avatarLimit={4}
|
||||
AvatarComponent={AvatarComponent}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
@ -0,0 +1,113 @@
|
||||
import type { FC } from 'react';
|
||||
import { styled } from '@mui/material';
|
||||
import type { ProjectSchema, ProjectSchemaOwners } from 'openapi';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import {
|
||||
AvatarGroup,
|
||||
AvatarComponent,
|
||||
} from 'component/common/AvatarGroup/AvatarGroup';
|
||||
|
||||
export interface IProjectOwnersProps {
|
||||
owners?: ProjectSchema['owners'];
|
||||
}
|
||||
|
||||
const useOwnersMap = () => {
|
||||
const { uiConfig } = useUiConfig();
|
||||
|
||||
return (
|
||||
owner: ProjectSchemaOwners[0],
|
||||
): {
|
||||
name: string;
|
||||
imageUrl?: string;
|
||||
email?: string;
|
||||
} => {
|
||||
if (owner.ownerType === 'user') {
|
||||
return {
|
||||
name: owner.name,
|
||||
imageUrl: owner.imageUrl || undefined,
|
||||
email: owner.email || undefined,
|
||||
};
|
||||
}
|
||||
if (owner.ownerType === 'group') {
|
||||
return {
|
||||
name: owner.name,
|
||||
};
|
||||
}
|
||||
return {
|
||||
name: 'System',
|
||||
imageUrl: `${uiConfig.unleashUrl}/logo-unleash.png`,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
const StyledUserName = styled('span')(({ theme }) => ({
|
||||
fontSize: theme.typography.body2.fontSize,
|
||||
alignSelf: 'end',
|
||||
lineHeight: 1,
|
||||
lineClamp: `1`,
|
||||
WebkitLineClamp: 1,
|
||||
display: '-webkit-box',
|
||||
boxOrient: 'vertical',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
alignItems: 'flex-start',
|
||||
WebkitBoxOrient: 'vertical',
|
||||
wordBreak: 'break-word',
|
||||
maxWidth: '100%',
|
||||
}));
|
||||
|
||||
const StyledContainer = styled('div')(() => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}));
|
||||
|
||||
const StyledOwnerName = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(0.25),
|
||||
margin: theme.spacing(0, 0, 0, 1),
|
||||
}));
|
||||
|
||||
const StyledHeader = styled('span')(({ theme }) => ({
|
||||
lineHeight: 1,
|
||||
fontSize: theme.fontSizes.smallerBody,
|
||||
color: theme.palette.text.secondary,
|
||||
fontWeight: theme.typography.fontWeightRegular,
|
||||
}));
|
||||
|
||||
const StyledWrapper = styled('div')(({ theme }) => ({
|
||||
padding: theme.spacing(1.5, 0, 1.5, 2),
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}));
|
||||
|
||||
const StyledAvatarComponent = styled(AvatarComponent)(({ theme }) => ({
|
||||
cursor: 'default',
|
||||
}));
|
||||
|
||||
export const ProjectOwners: FC<IProjectOwnersProps> = ({ owners = [] }) => {
|
||||
const ownersMap = useOwnersMap();
|
||||
const users = owners.map(ownersMap);
|
||||
|
||||
return (
|
||||
<StyledWrapper data-testid='test'>
|
||||
<StyledContainer>
|
||||
<AvatarGroup
|
||||
users={users}
|
||||
avatarLimit={4}
|
||||
AvatarComponent={StyledAvatarComponent}
|
||||
/>
|
||||
</StyledContainer>
|
||||
<ConditionallyRender
|
||||
condition={owners.length === 1}
|
||||
show={
|
||||
<StyledOwnerName>
|
||||
<StyledHeader>Owner</StyledHeader>
|
||||
<StyledUserName>{users[0]?.name}</StyledUserName>
|
||||
</StyledOwnerName>
|
||||
}
|
||||
/>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
@ -0,0 +1,80 @@
|
||||
import type { FC } from 'react';
|
||||
import { useLastSeenColors } from 'component/feature/FeatureView/FeatureEnvironmentSeen/useLastSeenColors';
|
||||
import { Box, styled, Typography } from '@mui/material';
|
||||
import { ReactComponent as UsageLine } from 'assets/icons/usage-line.svg';
|
||||
import { ReactComponent as UsageRate } from 'assets/icons/usage-rate.svg';
|
||||
import { StyledIconWrapper } from 'component/feature/FeatureView/FeatureEnvironmentSeen/FeatureEnvironmentSeen';
|
||||
import { flexRow } from 'themes/themeStyles';
|
||||
import { TimeAgo } from 'component/common/TimeAgo/TimeAgo';
|
||||
import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip';
|
||||
|
||||
type ProjectLastSeenProps = {
|
||||
date?: Date | number | string | null;
|
||||
};
|
||||
|
||||
const StyledContainer = styled(Box)(({ theme }) => ({
|
||||
...flexRow,
|
||||
justifyContent: 'flex-start',
|
||||
textWrap: 'nowrap',
|
||||
width: '50%',
|
||||
gap: theme.spacing(1),
|
||||
cursor: 'default',
|
||||
}));
|
||||
|
||||
const StyledIcon = styled(StyledIconWrapper)<{ background: string }>(
|
||||
({ background }) => ({
|
||||
background,
|
||||
margin: 0,
|
||||
}),
|
||||
);
|
||||
|
||||
const Title = () => (
|
||||
<>
|
||||
<Typography
|
||||
component='span'
|
||||
sx={(theme) => ({ fontSize: theme.fontSizes.smallBody })}
|
||||
>
|
||||
Last usage reported
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={(theme) => ({
|
||||
color: theme.palette.text.secondary,
|
||||
fontSize: theme.fontSizes.smallerBody,
|
||||
})}
|
||||
>
|
||||
Across all flags in your project this is the last time usage metrics
|
||||
where reported from connected applications.
|
||||
</Typography>
|
||||
</>
|
||||
);
|
||||
|
||||
export const ProjectLastSeen: FC<ProjectLastSeenProps> = ({ date }) => {
|
||||
const getColor = useLastSeenColors();
|
||||
const { text, background } = getColor(date);
|
||||
|
||||
if (!date) {
|
||||
return (
|
||||
<HtmlTooltip title={<Title />} arrow>
|
||||
<StyledContainer
|
||||
sx={(theme) => ({ color: theme.palette.text.secondary })}
|
||||
>
|
||||
<StyledIcon background={background}>
|
||||
<UsageLine stroke={text} />
|
||||
</StyledIcon>{' '}
|
||||
<div>No activity</div>
|
||||
</StyledContainer>
|
||||
</HtmlTooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<HtmlTooltip title={<Title />} arrow>
|
||||
<StyledContainer>
|
||||
<StyledIcon background={background}>
|
||||
<UsageRate stroke={text} />
|
||||
</StyledIcon>{' '}
|
||||
<TimeAgo date={date} refresh={false} />
|
||||
</StyledContainer>
|
||||
</HtmlTooltip>
|
||||
);
|
||||
};
|
@ -1,15 +1,39 @@
|
||||
import type { FC } from 'react';
|
||||
import LockIcon from '@mui/icons-material/Lock';
|
||||
import ProtectedProjectIcon from '@mui/icons-material/LockOutlined';
|
||||
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
|
||||
import PrivateProjectIcon from '@mui/icons-material/VisibilityOffOutlined';
|
||||
import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip';
|
||||
import { Badge } from 'component/common/Badge/Badge';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
import { styled } from '@mui/material';
|
||||
|
||||
interface IProjectModeBadgeProps {
|
||||
mode?: 'private' | 'protected' | 'public' | string;
|
||||
}
|
||||
|
||||
const StyledIcon = styled('div')(({ theme }) => ({
|
||||
color: theme.palette.primary.main,
|
||||
fontSize: theme.spacing(2.25),
|
||||
paddingTop: theme.spacing(0.75),
|
||||
}));
|
||||
|
||||
export const ProjectModeBadge: FC<IProjectModeBadgeProps> = ({ mode }) => {
|
||||
const projectListImprovementsEnabled = useUiFlag('projectListImprovements');
|
||||
|
||||
if (mode === 'private') {
|
||||
if (projectListImprovementsEnabled) {
|
||||
return (
|
||||
<HtmlTooltip
|
||||
title="This project's collaboration mode is set to private. The project and associated feature flags can only be seen by members of the project."
|
||||
arrow
|
||||
>
|
||||
<StyledIcon>
|
||||
<PrivateProjectIcon fontSize='inherit' />
|
||||
</StyledIcon>
|
||||
</HtmlTooltip>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<HtmlTooltip
|
||||
title="This project's collaboration mode is set to private. The project and associated feature flags can only be seen by members of the project."
|
||||
@ -21,6 +45,18 @@ export const ProjectModeBadge: FC<IProjectModeBadgeProps> = ({ mode }) => {
|
||||
}
|
||||
|
||||
if (mode === 'protected') {
|
||||
if (projectListImprovementsEnabled) {
|
||||
return (
|
||||
<HtmlTooltip
|
||||
title="This project's collaboration mode is set to protected. Only admins and project members can submit change requests."
|
||||
arrow
|
||||
>
|
||||
<StyledIcon>
|
||||
<ProtectedProjectIcon fontSize='inherit' />
|
||||
</StyledIcon>
|
||||
</HtmlTooltip>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<HtmlTooltip
|
||||
title="This project's collaboration mode is set to protected. Only admins and project members can submit change requests."
|
||||
|
@ -2,11 +2,13 @@ import type { ComponentType } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { ProjectCard as LegacyProjectCard } from '../ProjectCard/LegacyProjectCard';
|
||||
import { ProjectCard as NewProjectCard } from '../ProjectCard/ProjectCard';
|
||||
|
||||
import type { IProjectCard } from 'interfaces/project';
|
||||
import loadingData from './loadingData';
|
||||
import { TablePlaceholder } from 'component/common/Table';
|
||||
import { styled, Typography } from '@mui/material';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
|
||||
const StyledGridContainer = styled('div')(({ theme }) => ({
|
||||
display: 'grid',
|
||||
@ -24,25 +26,30 @@ const StyledCardLink = styled(Link)(({ theme }) => ({
|
||||
pointer: 'cursor',
|
||||
}));
|
||||
|
||||
type ProjectGroupProps<T extends { id: string } = IProjectCard> = {
|
||||
type ProjectGroupProps = {
|
||||
sectionTitle?: string;
|
||||
projects: T[];
|
||||
projects: IProjectCard[];
|
||||
loading: boolean;
|
||||
searchValue: string;
|
||||
placeholder?: string;
|
||||
ProjectCardComponent?: ComponentType<T & any>;
|
||||
ProjectCardComponent?: ComponentType<IProjectCard & any>;
|
||||
link?: boolean;
|
||||
};
|
||||
|
||||
export const ProjectGroup = <T extends { id: string }>({
|
||||
export const ProjectGroup = ({
|
||||
sectionTitle,
|
||||
projects,
|
||||
loading,
|
||||
searchValue,
|
||||
placeholder = 'No projects available.',
|
||||
ProjectCardComponent = LegacyProjectCard,
|
||||
ProjectCardComponent,
|
||||
link = true,
|
||||
}: ProjectGroupProps<T>) => {
|
||||
}: ProjectGroupProps) => {
|
||||
const projectListImprovementsEnabled = useUiFlag('projectListImprovements');
|
||||
const ProjectCard =
|
||||
ProjectCardComponent ??
|
||||
(projectListImprovementsEnabled ? NewProjectCard : LegacyProjectCard);
|
||||
|
||||
return (
|
||||
<article>
|
||||
<ConditionallyRender
|
||||
@ -82,9 +89,9 @@ export const ProjectGroup = <T extends { id: string }>({
|
||||
<>
|
||||
{loadingData.map(
|
||||
(project: IProjectCard) => (
|
||||
<LegacyProjectCard
|
||||
<ProjectCard
|
||||
data-loading
|
||||
onHover={() => {}}
|
||||
createdAt={project.createdAt}
|
||||
key={project.id}
|
||||
name={project.name}
|
||||
id={project.id}
|
||||
@ -99,21 +106,17 @@ export const ProjectGroup = <T extends { id: string }>({
|
||||
)}
|
||||
elseShow={() => (
|
||||
<>
|
||||
{projects.map((project: T) =>
|
||||
{projects.map((project) =>
|
||||
link ? (
|
||||
<StyledCardLink
|
||||
key={project.id}
|
||||
to={`/projects/${project.id}`}
|
||||
>
|
||||
<ProjectCardComponent
|
||||
onHover={() => {}}
|
||||
{...project}
|
||||
/>
|
||||
<ProjectCard {...project} />
|
||||
</StyledCardLink>
|
||||
) : (
|
||||
<ProjectCardComponent
|
||||
<ProjectCard
|
||||
key={project.id}
|
||||
onHover={() => {}}
|
||||
{...project}
|
||||
/>
|
||||
),
|
||||
|
@ -7,13 +7,16 @@ export interface IProjectCard {
|
||||
name: string;
|
||||
id: string;
|
||||
createdAt: string;
|
||||
health: number;
|
||||
description: string;
|
||||
featureCount: number;
|
||||
mode: string;
|
||||
health?: number;
|
||||
description?: string;
|
||||
featureCount?: number;
|
||||
mode?: string;
|
||||
memberCount?: number;
|
||||
onHover?: () => void;
|
||||
favorite?: boolean;
|
||||
owners?: ProjectSchema['owners'];
|
||||
lastUpdatedAt?: Date;
|
||||
lastReportedFlagUsage?: Date;
|
||||
}
|
||||
|
||||
export type FeatureNamingType = {
|
||||
|
Loading…
Reference in New Issue
Block a user