Skip to content

Commit

Permalink
Projects archive UI (#7842)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tymek authored Aug 13, 2024
1 parent 3c45a4b commit f2b7e02
Showing 16 changed files with 437 additions and 77 deletions.
Original file line number Diff line number Diff line change
@@ -32,7 +32,7 @@ const BreadcrumbNav = () => {
const { isAdmin } = useContext(AccessContext);
const location = useLocation();

const paths = location.pathname
let paths = location.pathname
.split('/')
.filter((item) => item)
.filter(
@@ -55,9 +55,15 @@ const BreadcrumbNav = () => {
.map(decodeURI);

if (location.pathname === '/insights') {
// Because of sticky header in Insights
return null;
}

if (paths.length === 1 && paths[0] === 'projects-archive') {
// It's not possible to use `projects/archive`, because it's :projectId path
paths = ['projects', 'archive'];
}

return (
<StyledBreadcrumbContainer>
<ConditionallyRender
7 changes: 4 additions & 3 deletions frontend/src/component/common/ProjectIcon/ProjectIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ComponentProps, FC } from 'react';
import { SvgIcon } from '@mui/material';
import { ReactComponent as Svg } from 'assets/icons/projectIconSmall.svg';

export const ProjectIcon = () => (
<SvgIcon component={Svg} viewBox={'0 0 14 10'} />
);
export const ProjectIcon: FC<ComponentProps<typeof SvgIcon>> = ({
...props
}) => <SvgIcon component={Svg} viewBox={'0 0 14 10'} {...props} />;
Original file line number Diff line number Diff line change
@@ -98,6 +98,13 @@ exports[`returns all baseRoutes 1`] = `
"title": "Projects",
"type": "protected",
},
{
"component": [Function],
"menu": {},
"path": "/projects-archive",
"title": "Projects archive",
"type": "protected",
},
{
"component": [Function],
"menu": {
8 changes: 8 additions & 0 deletions frontend/src/component/menu/routes.ts
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ import { NewUser } from 'component/user/NewUser/NewUser';
import ResetPassword from 'component/user/ResetPassword/ResetPassword';
import ForgottenPassword from 'component/user/ForgottenPassword/ForgottenPassword';
import { ProjectListNew } from 'component/project/ProjectList/ProjectList';
import { ArchiveProjectList } from 'component/project/ProjectList/ArchiveProjectList';
import RedirectArchive from 'component/archive/RedirectArchive';
import CreateEnvironment from 'component/environments/CreateEnvironment/CreateEnvironment';
import EditEnvironment from 'component/environments/EditEnvironment/EditEnvironment';
@@ -125,6 +126,13 @@ export const routes: IRoute[] = [
type: 'protected',
menu: { mobile: true },
},
{
path: '/projects-archive',
title: 'Projects archive',
component: ArchiveProjectList,
type: 'protected',
menu: {},
},

// Features
{
Original file line number Diff line number Diff line change
@@ -4,22 +4,27 @@ import Delete from '@mui/icons-material/Delete';
import Edit from '@mui/icons-material/Edit';
import { flexRow } from 'themes/themeStyles';

export const StyledProjectCard = styled(Card)(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
height: '100%',
boxShadow: 'none',
border: `1px solid ${theme.palette.divider}`,
[theme.breakpoints.down('sm')]: {
justifyContent: 'center',
},
'&:hover': {
export const StyledProjectCard = styled(Card)<{ disabled?: boolean }>(
({ theme, disabled = false }) => ({
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
height: '100%',
boxShadow: 'none',
border: `1px solid ${theme.palette.divider}`,
[theme.breakpoints.down('sm')]: {
justifyContent: 'center',
},
transition: 'background-color 0.2s ease-in-out',
backgroundColor: theme.palette.neutral.light,
},
borderRadius: theme.shape.borderRadiusMedium,
}));
backgroundColor: disabled
? theme.palette.neutral.light
: theme.palette.background.default,
'&:hover': {
backgroundColor: theme.palette.neutral.light,
},
borderRadius: theme.shape.borderRadiusMedium,
}),
);

export const StyledProjectCardBody = styled(Box)(({ theme }) => ({
padding: theme.spacing(1, 2, 2, 2),
@@ -72,11 +77,13 @@ export const StyledDivInfo = styled('div')(({ theme }) => ({
padding: theme.spacing(0, 1),
}));

export const StyledParagraphInfo = styled('p')(({ theme }) => ({
color: theme.palette.primary.dark,
fontWeight: 'bold',
fontSize: theme.typography.body1.fontSize,
}));
export const StyledParagraphInfo = styled('p')<{ disabled?: boolean }>(
({ theme, disabled = false }) => ({
color: disabled ? 'inherit' : theme.palette.primary.dark,
fontWeight: disabled ? 'normal' : 'bold',
fontSize: theme.typography.body1.fontSize,
}),
);

export const StyledIconBox = styled(Box)(({ theme }) => ({
display: 'grid',
@@ -87,3 +94,8 @@ export const StyledIconBox = styled(Box)(({ theme }) => ({
color: theme.palette.primary.main,
height: '100%',
}));

export const StyledActions = styled(Box)(({ theme }) => ({
display: 'flex',
marginRight: theme.spacing(2),
}));
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@ interface IProjectCardProps {
name: string;
featureCount: number;
health: number;
memberCount: number;
memberCount?: number;
id: string;
onHover: () => void;
isFavorite?: boolean;
@@ -32,7 +32,7 @@ export const ProjectCard = ({
name,
featureCount,
health,
memberCount,
memberCount = 0,
onHover,
id,
mode,
140 changes: 140 additions & 0 deletions frontend/src/component/project/NewProjectCard/ProjectArchiveCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import type { FC } from 'react';
import {
StyledProjectCard,
StyledDivHeader,
StyledBox,
StyledCardTitle,
StyledDivInfo,
StyledParagraphInfo,
StyledProjectCardBody,
StyledIconBox,
StyledActions,
} from './NewProjectCard.styles';
import { ProjectCardFooter } from './ProjectCardFooter/ProjectCardFooter';
import { ProjectModeBadge } from './ProjectModeBadge/ProjectModeBadge';
import { ProjectOwners } from './ProjectOwners/ProjectOwners';
import type { ProjectSchemaOwners } from 'openapi';
import { ProjectIcon } from 'component/common/ProjectIcon/ProjectIcon';
import { formatDateYMDHM } from 'utils/formatDate';
import { useLocationSettings } from 'hooks/useLocationSettings';
import { parseISO } from 'date-fns';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import TimeAgo from 'react-timeago';
import { Box, Link, Tooltip } from '@mui/material';
import { Link as RouterLink } from 'react-router-dom';
import {
CREATE_PROJECT,
DELETE_PROJECT,
} from 'component/providers/AccessProvider/permissions';
import Undo from '@mui/icons-material/Undo';
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
import Delete from '@mui/icons-material/Delete';

interface IProjectArchiveCardProps {
id: string;
name: string;
createdAt?: string;
archivedAt?: string;
featureCount: number;
onRevive: () => void;
onDelete: () => void;
mode: string;
owners?: ProjectSchemaOwners;
}

export const ProjectArchiveCard: FC<IProjectArchiveCardProps> = ({
id,
name,
archivedAt,
featureCount = 0,
onRevive,
onDelete,
mode,
owners,
}) => {
const { locationSettings } = useLocationSettings();
const Actions: FC<{
id: string;
}> = ({ id }) => (
<StyledActions>
<PermissionIconButton
onClick={onRevive}
projectId={id}
permission={CREATE_PROJECT}
tooltipProps={{ title: 'Restore project' }}
data-testid={`revive-feature-flag-button`}
>
<Undo />
</PermissionIconButton>
<PermissionIconButton
permission={DELETE_PROJECT}
projectId={id}
tooltipProps={{ title: 'Permanently delete project' }}
onClick={onDelete}
>
<Delete />
</PermissionIconButton>
</StyledActions>
);

return (
<StyledProjectCard disabled>
<StyledProjectCardBody>
<StyledDivHeader>
<StyledIconBox>
<ProjectIcon color='action' />
</StyledIconBox>
<StyledBox data-loading>
<StyledCardTitle>{name}</StyledCardTitle>
</StyledBox>
<ProjectModeBadge mode={mode} />
</StyledDivHeader>
<StyledDivInfo>
<Link
component={RouterLink}
to={`/archive?search=project%3A${encodeURI(id)}`}
>
<StyledParagraphInfo disabled data-loading>
{featureCount}
</StyledParagraphInfo>
<p data-loading>
archived {featureCount === 1 ? 'flag' : 'flags'}
</p>
</Link>
<ConditionallyRender
condition={Boolean(archivedAt)}
show={
<Tooltip
title={formatDateYMDHM(
parseISO(archivedAt as string),
locationSettings.locale,
)}
arrow
>
<Box
sx={(theme) => ({
color: theme.palette.text.secondary,
})}
>
<StyledParagraphInfo disabled data-loading>
Archived
</StyledParagraphInfo>
<p data-loading>
<TimeAgo
date={
new Date(archivedAt as string)
}
/>
</p>
</Box>
</Tooltip>
}
/>
</StyledDivInfo>
</StyledProjectCardBody>
<ProjectCardFooter id={id} Actions={Actions} disabled>
<ProjectOwners owners={owners} />
</ProjectCardFooter>
</StyledProjectCard>
);
};
Original file line number Diff line number Diff line change
@@ -10,26 +10,36 @@ interface IProjectCardFooterProps {
id: string;
isFavorite?: boolean;
children?: React.ReactNode;
Actions?: FC<{ id: string; isFavorite?: boolean }>;
disabled?: boolean;
}

const StyledFooter = styled(Box)(({ theme }) => ({
display: 'grid',
gridTemplateColumns: 'auto 1fr auto',
const StyledFooter = styled(Box)<{ disabled: boolean }>(
({ theme, disabled }) => ({
display: 'flex',
background: disabled
? theme.palette.background.paper
: theme.palette.envAccordion.expanded,
boxShadow: theme.boxShadows.accordionFooter,
alignItems: 'center',
justifyContent: 'space-between',
borderTop: `1px solid ${theme.palette.divider}`,
}),
);

const StyledContainer = styled(Box)(({ theme }) => ({
padding: theme.spacing(1.5, 0, 2.5, 3),
display: 'flex',
alignItems: 'center',
padding: theme.spacing(1.5, 3, 2.5, 3),
background: theme.palette.envAccordion.expanded,
boxShadow: theme.boxShadows.accordionFooter,
}));

const StyledFavoriteIconButton = styled(FavoriteIconButton)(({ theme }) => ({
marginRight: theme.spacing(-1),
marginBottom: theme.spacing(-1),
margin: theme.spacing(1, 2, 0, 0),
}));

export const ProjectCardFooter: FC<IProjectCardFooterProps> = ({
children,
const FavoriteAction: FC<{ id: string; isFavorite?: boolean }> = ({
id,
isFavorite = false,
isFavorite,
}) => {
const { setToastApiError } = useToast();
const { favorite, unfavorite } = useFavoriteProjectsApi();
@@ -48,14 +58,27 @@ export const ProjectCardFooter: FC<IProjectCardFooterProps> = ({
setToastApiError('Something went wrong, could not update favorite');
}
};

return (
<StyledFavoriteIconButton
onClick={onFavorite}
isFavorite={Boolean(isFavorite)}
size='medium'
/>
);
};

export const ProjectCardFooter: FC<IProjectCardFooterProps> = ({
children,
id,
isFavorite = false,
Actions = FavoriteAction,
disabled = false,
}) => {
return (
<StyledFooter>
{children}
<StyledFavoriteIconButton
onClick={onFavorite}
isFavorite={isFavorite}
size='medium'
/>
<StyledFooter disabled={disabled}>
<StyledContainer>{children}</StyledContainer>
<Actions id={id} isFavorite={isFavorite} />
</StyledFooter>
);
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { VFC } from 'react';
import type { FC } from 'react';
import LockIcon from '@mui/icons-material/Lock';
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip';
@@ -8,7 +8,7 @@ interface IProjectModeBadgeProps {
mode: 'private' | 'protected' | 'public' | string;
}

export const ProjectModeBadge: VFC<IProjectModeBadgeProps> = ({ mode }) => {
export const ProjectModeBadge: FC<IProjectModeBadgeProps> = ({ mode }) => {
if (mode === 'private') {
return (
<HtmlTooltip
Loading

0 comments on commit f2b7e02

Please sign in to comment.