import { useNavigate } from 'react-router'; import useLoading from 'hooks/useLoading'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ReactComponent as ImportSvg } from 'assets/icons/import.svg'; 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 PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; import { UPDATE_FEATURE } from 'component/providers/AccessProvider/permissions'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import { Navigate, Route, Routes, useLocation } 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 { IMPORT_BUTTON } from 'utils/testIds'; 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 { useUiFlag } from 'hooks/useUiFlag'; 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: '2px', }, flex: 'auto', justifyContent: 'center', minHeight: '1.5em', alignItems: 'center', })); const TabText = styled('span')(({ theme }) => ({ color: theme.palette.text.primary, })); const ChangeRequestsLabel = () => { const simplifyProjectOverview = useUiFlag('simplifyProjectOverview'); const projectId = useRequiredPathParam('projectId'); const { total } = useActionableChangeRequests(projectId); if (!simplifyProjectOverview) { return 'Change requests'; } 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, }, })); 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 simplifyProjectOverview = useUiFlag('simplifyProjectOverview'); const [projectStatusOpen, setProjectStatusOpen] = useState(false); const [showDelDialog, setShowDelDialog] = useState(false); const [ changeRequestChangesWillOverwrite, setChangeRequestChangesWillOverwrite, ] = useState(false); const tabs: ITab[] = [ { title: 'Flags', path: basePath, name: 'flags', }, { title: 'Insights', path: `${basePath}/insights`, name: 'insights', }, { title: 'Health', path: `${basePath}/health`, name: 'health', }, ...(simplifyProjectOverview ? [] : [ { title: 'Archived flags', path: `${basePath}/archive`, name: 'archive', } as ITab, ]), { 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: 'Project 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', title: 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} setModalOpen(true)} tooltipProps={{ title: 'Import' }} data-testid={IMPORT_BUTTON} data-loading-project > } /> {simplifyProjectOverview && ( setProjectStatusOpen(true)} startIcon={} data-loading-project > Project status )} {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), }} > } /> } /> } /> } /> setProjectStatusOpen(false)} />
); };