1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-19 01:17:18 +02:00
unleash.unleash/frontend/src/component/project/ProjectList/ProjectList.tsx
Thomas Heartman 06de5de85c
chore: code cleanup for new project form pt 2 (#7190)
This is the second part of the code cleanup job. It primarily consists
of breaking apart large files and organizing the code better
2024-05-29 08:10:47 +02:00

263 lines
9.2 KiB
TypeScript

import { useContext, useEffect, useMemo, useState } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import useProjects from 'hooks/api/getters/useProjects/useProjects';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import type { IProjectCard } from 'interfaces/project';
import { PageContent } from 'component/common/PageContent/PageContent';
import AccessContext from 'contexts/AccessContext';
import { PageHeader } from 'component/common/PageHeader/PageHeader';
import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton';
import { CREATE_PROJECT } from 'component/providers/AccessProvider/permissions';
import Add from '@mui/icons-material/Add';
import ApiError from 'component/common/ApiError/ApiError';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { styled, useMediaQuery } from '@mui/material';
import theme from 'themes/theme';
import { Search } from 'component/common/Search/Search';
import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature';
import type { ITooltipResolverProps } from 'component/common/TooltipResolver/TooltipResolver';
import { ReactComponent as ProPlanIcon } from 'assets/icons/pro-enterprise-feature-badge.svg';
import { ReactComponent as ProPlanIconLight } from 'assets/icons/pro-enterprise-feature-badge-light.svg';
import { safeRegExp } from '@server/util/escape-regex';
import { ThemeMode } from 'component/common/ThemeMode/ThemeMode';
import { useUiFlag } from 'hooks/useUiFlag';
import { useProfile } from 'hooks/api/getters/useProfile/useProfile';
import { groupProjects } from './group-projects';
import { ProjectGroup } from './ProjectGroup';
import { CreateProjectDialog } from '../Project/CreateProject/NewCreateProjectForm/CreateProjectDialog';
const StyledApiError = styled(ApiError)(({ theme }) => ({
maxWidth: '500px',
marginBottom: theme.spacing(2),
}));
const StyledContainer = styled('div')(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(4),
}));
type PageQueryType = Partial<Record<'search', string>>;
type projectMap = {
[index: string]: boolean;
};
interface ICreateButtonData {
disabled: boolean;
tooltip?: Omit<ITooltipResolverProps, 'children'>;
endIcon?: React.ReactNode;
}
const NAVIGATE_TO_CREATE_PROJECT = 'NAVIGATE_TO_CREATE_PROJECT';
function resolveCreateButtonData(
isOss: boolean,
hasAccess: boolean,
): ICreateButtonData {
if (isOss) {
return {
disabled: true,
tooltip: {
titleComponent: (
<PremiumFeature feature='adding-new-projects' tooltip />
),
sx: { maxWidth: '320px' },
variant: 'custom',
},
endIcon: (
<ThemeMode
darkmode={<ProPlanIconLight />}
lightmode={<ProPlanIcon />}
/>
),
};
} else if (!hasAccess) {
return {
tooltip: {
title: 'You do not have permission to create new projects',
},
disabled: true,
};
} else {
return {
tooltip: { title: 'Click to create a new project' },
disabled: false,
};
}
}
export const ProjectListNew = () => {
const { hasAccess } = useContext(AccessContext);
const navigate = useNavigate();
const { projects, loading, error, refetch } = useProjects();
const { isOss } = useUiConfig();
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
const [searchParams, setSearchParams] = useSearchParams();
const [searchValue, setSearchValue] = useState(
searchParams.get('search') || '',
);
const splitProjectList = useUiFlag('projectListFilterMyProjects');
const myProjects = new Set(useProfile().profile?.projects || []);
const showCreateDialog = Boolean(searchParams.get('create'));
const [openCreateDialog, setOpenCreateDialog] = useState(showCreateDialog);
const useNewProjectForm = useUiFlag('newCreateProjectUI');
useEffect(() => {
const tableState: PageQueryType = {};
if (searchValue) {
tableState.search = searchValue;
}
setSearchParams(tableState, {
replace: true,
});
}, [searchValue, setSearchParams]);
const filteredProjects = useMemo(() => {
const regExp = safeRegExp(searchValue, 'i');
return (
searchValue
? projects.filter((project) => regExp.test(project.name))
: projects
).sort((a, b) => {
if (a?.favorite && !b?.favorite) {
return -1;
}
if (!a?.favorite && b?.favorite) {
return 1;
}
return 0;
});
}, [projects, searchValue]);
const groupedProjects = useMemo(() => {
if (!splitProjectList) {
return { myProjects: [], otherProjects: filteredProjects };
}
return groupProjects(myProjects, filteredProjects);
}, [filteredProjects, myProjects, splitProjectList]);
const createButtonData = resolveCreateButtonData(
isOss(),
hasAccess(CREATE_PROJECT),
);
const projectCount =
filteredProjects.length < projects.length
? `${filteredProjects.length} of ${projects.length}`
: projects.length;
const ProjectGroupComponent = (props: {
sectionTitle?: string;
projects: IProjectCard[];
}) => {
return (
<ProjectGroup
loading={loading}
searchValue={searchValue}
{...props}
/>
);
};
function handleClick() {
if (useNewProjectForm) {
return setOpenCreateDialog(true);
}
navigate('/projects/create');
}
return (
<PageContent
isLoading={loading}
header={
<PageHeader
title={`Projects (${projectCount})`}
actions={
<>
<ConditionallyRender
condition={!isSmallScreen}
show={
<>
<Search
initialValue={searchValue}
onChange={setSearchValue}
/>
<PageHeader.Divider />
</>
}
/>
<ResponsiveButton
Icon={Add}
endIcon={createButtonData.endIcon}
onClick={handleClick}
maxWidth='700px'
permission={CREATE_PROJECT}
disabled={createButtonData.disabled}
tooltipProps={createButtonData.tooltip}
data-testid={NAVIGATE_TO_CREATE_PROJECT}
>
New project
</ResponsiveButton>
</>
}
>
<ConditionallyRender
condition={isSmallScreen}
show={
<Search
initialValue={searchValue}
onChange={setSearchValue}
/>
}
/>
</PageHeader>
}
>
<StyledContainer>
<ConditionallyRender
condition={error}
show={() => (
<StyledApiError
onClick={refetch}
text='Error fetching projects'
/>
)}
/>
<ConditionallyRender
condition={splitProjectList}
show={
<>
<ProjectGroupComponent
sectionTitle='My projects'
projects={groupedProjects.myProjects}
/>
<ProjectGroupComponent
sectionTitle='Other projects'
projects={groupedProjects.otherProjects}
/>
</>
}
elseShow={
<ProjectGroupComponent projects={filteredProjects} />
}
/>
</StyledContainer>
<ConditionallyRender
condition={useNewProjectForm}
show={
<CreateProjectDialog
open={openCreateDialog}
onClose={() => setOpenCreateDialog(false)}
/>
}
/>
</PageContent>
);
};