import { useNavigate } from 'react-router';
import useLoading from 'hooks/useLoading';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { ReactComponent as ProjectStatusSvg } from 'assets/icons/projectStatus.svg';
import {
StyledDiv,
StyledFavoriteIconButton,
StyledHeader,
StyledInnerContainer,
StyledName,
StyledProjectTitle,
StyledSeparator,
StyledTab,
StyledTabContainer,
StyledTopRow,
} from './Project.styles';
import {
Badge as CounterBadge,
Box,
Paper,
Tabs,
Typography,
styled,
Button,
} from '@mui/material';
import useToast from 'hooks/useToast';
import useQueryParams from 'hooks/useQueryParams';
import { useEffect, useState, type ReactNode } from 'react';
import ProjectEnvironment from '../ProjectEnvironment/ProjectEnvironment';
import { ProjectFeaturesArchive } from './ProjectFeaturesArchive/ProjectFeaturesArchive';
import ProjectFlags from './ProjectFlags';
import ProjectHealth from './ProjectHealth/ProjectHealth';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import {
Navigate,
Route,
Routes,
useLocation,
useSearchParams,
} from 'react-router-dom';
import { DeleteProjectDialogue } from './DeleteProject/DeleteProjectDialogue';
import { ProjectLog } from './ProjectLog/ProjectLog';
import { ChangeRequestOverview } from 'component/changeRequest/ChangeRequestOverview/ChangeRequestOverview';
import { ProjectChangeRequests } from '../../changeRequest/ProjectChangeRequests/ProjectChangeRequests';
import { ProjectSettings } from './ProjectSettings/ProjectSettings';
import { useFavoriteProjectsApi } from 'hooks/api/actions/useFavoriteProjectsApi/useFavoriteProjectsApi';
import { ImportModal } from './Import/ImportModal';
import { EnterpriseBadge } from 'component/common/EnterpriseBadge/EnterpriseBadge';
import { Badge } from 'component/common/Badge/Badge';
import type { UiFlags } from 'interfaces/uiConfig';
import { HiddenProjectIconWithTooltip } from './HiddenProjectIconWithTooltip/HiddenProjectIconWithTooltip';
import { ChangeRequestPlausibleProvider } from 'component/changeRequest/ChangeRequestContext';
import { ProjectApplications } from '../ProjectApplications/ProjectApplications';
import { ProjectInsights } from './ProjectInsights/ProjectInsights';
import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview';
import { ProjectArchived } from './ArchiveProject/ProjectArchived';
import { usePlausibleTracker } from '../../../hooks/usePlausibleTracker';
import { useActionableChangeRequests } from 'hooks/api/getters/useActionableChangeRequests/useActionableChangeRequests';
import { ProjectStatusModal } from './ProjectStatus/ProjectStatusModal';
const StyledBadge = styled(Badge)(({ theme }) => ({
position: 'absolute',
top: 5,
right: 20,
[theme.breakpoints.down('md')]: {
top: 2,
},
}));
interface ITab {
title: string;
path: string;
ossPath?: string;
name: string;
flag?: keyof UiFlags;
new?: boolean;
isEnterprise?: boolean;
labelOverride?: () => ReactNode;
}
const StyledCounterBadge = styled(CounterBadge)(({ theme }) => ({
'.MuiBadge-badge': {
backgroundColor: theme.palette.background.alternative,
right: '-4px',
},
[theme.breakpoints.down('md')]: {
right: '6px',
},
flex: 'auto',
justifyContent: 'center',
minHeight: '1.5em',
alignItems: 'center',
}));
const TabText = styled('span')(({ theme }) => ({
color: theme.palette.text.primary,
}));
const ChangeRequestsLabel = () => {
const projectId = useRequiredPathParam('projectId');
const { total } = useActionableChangeRequests(projectId);
return (
Change requests
);
};
const ProjectStatusButton = styled(Button)(({ theme }) => ({
color: theme.palette.text.primary,
fontSize: theme.typography.body1.fontSize,
fontWeight: 'bold',
'svg *': {
fill: theme.palette.primary.main,
},
}));
const ProjectStatusSvgWithMargin = styled(ProjectStatusSvg)(({ theme }) => ({
marginLeft: theme.spacing(0.5),
}));
const ProjectStatus = () => {
const [searchParams, setSearchParams] = useSearchParams();
const [projectStatusOpen, setProjectStatusOpen] = useState(
searchParams.has('project-status'),
);
const openStatusModal = () => {
searchParams.set('project-status', '');
setSearchParams(searchParams);
setProjectStatusOpen(true);
};
const closeStatusModal = () => {
searchParams.delete('project-status');
setSearchParams(searchParams);
setProjectStatusOpen(false);
};
return (
<>
}
data-loading-project
>
Project status
setProjectStatusOpen(false)}
/>
>
);
};
export const Project = () => {
const projectId = useRequiredPathParam('projectId');
const { trackEvent } = usePlausibleTracker();
const params = useQueryParams();
const { project, loading, error, refetch } = useProjectOverview(projectId);
const ref = useLoading(loading, '[data-loading-project=true]');
const { setToastData, setToastApiError } = useToast();
const [modalOpen, setModalOpen] = useState(false);
const navigate = useNavigate();
const { pathname } = useLocation();
const { isOss, uiConfig, isPro } = useUiConfig();
const basePath = `/projects/${projectId}`;
const projectName = project?.name || projectId;
const { favorite, unfavorite } = useFavoriteProjectsApi();
const [showDelDialog, setShowDelDialog] = useState(false);
const [
changeRequestChangesWillOverwrite,
setChangeRequestChangesWillOverwrite,
] = useState(false);
const tabs: ITab[] = [
{
title: 'Overview',
path: basePath,
name: 'flags',
},
{
title: 'Change requests',
path: `${basePath}/change-requests`,
name: 'change-request',
isEnterprise: true,
labelOverride: ChangeRequestsLabel,
},
{
title: 'Applications',
path: `${basePath}/applications`,
name: 'applications',
},
{
title: 'Event log',
path: `${basePath}/logs`,
name: 'logs',
},
{
title: 'Settings',
path: `${basePath}/settings`,
ossPath: `${basePath}/settings/api-access`,
name: 'settings',
},
];
const filteredTabs = tabs
.filter((tab) => {
if (tab.flag) {
return uiConfig.flags[tab.flag];
}
return true;
})
.filter((tab) => !(isOss() && tab.isEnterprise));
const activeTab = [...filteredTabs]
.reverse()
.find((tab) => pathname.startsWith(tab.path));
useEffect(() => {
const created = params.get('created');
const edited = params.get('edited');
if (created || edited) {
const text = created ? 'Project created' : 'Project updated';
setToastData({
type: 'success',
text,
});
}
/* eslint-disable-next-line */
}, []);
if (error?.status === 404) {
return (
({ padding: theme.spacing(2, 4, 4) })}>
404 Not Found
Project {projectId} does not exist.
);
}
const onFavorite = async () => {
try {
if (project?.favorite) {
await unfavorite(projectId);
} else {
await favorite(projectId);
}
refetch();
} catch (error) {
setToastApiError('Something went wrong, could not update favorite');
}
};
const enterpriseIcon = (
({
marginLeft: theme.spacing(1),
display: 'flex',
})}
>
);
if (project.archivedAt) {
return ;
}
return (
}
/>
{projectName}
{filteredTabs.map((tab) => {
return (
) : (
tab.title
)
}
value={tab.path}
onClick={() => {
if (tab.title !== 'Flags') {
trackEvent('project-navigation', {
props: {
eventType: tab.title,
},
});
}
navigate(
isOss() && tab.ossPath
? tab.ossPath
: tab.path,
);
}}
data-testid={`TAB_${tab.title}`}
iconPosition={
tab.isEnterprise ? 'end' : undefined
}
icon={
<>
Beta
}
/>
{(tab.isEnterprise &&
isPro() &&
enterpriseIcon) ||
undefined}
>
}
/>
);
})}
{
setShowDelDialog(false);
}}
onSuccess={() => {
navigate('/projects');
}}
/>
} />
}
/>
} />
} />
} />
} />
}
/>
setChangeRequestChangesWillOverwrite(true),
}}
>
}
/>
} />
} />
} />
);
};