1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-23 00:22:19 +01:00
unleash.unleash/frontend/src/component/personalDashboard/PersonalDashboard.tsx

325 lines
11 KiB
TypeScript
Raw Normal View History

2024-09-19 12:37:35 +02:00
import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
import {
IconButton,
Link,
2024-09-19 12:37:35 +02:00
List,
ListItem,
ListItemButton,
styled,
Typography,
2024-09-19 12:37:35 +02:00
} from '@mui/material';
import React, { type FC, useEffect, useRef } from 'react';
import LinkIcon from '@mui/icons-material/ArrowForward';
import { WelcomeDialog } from './WelcomeDialog';
import { useLocalStorageState } from 'hooks/useLocalStorageState';
import { usePersonalDashboard } from 'hooks/api/getters/usePersonalDashboard/usePersonalDashboard';
import { getFeatureTypeIcons } from 'utils/getFeatureTypeIcons';
import type {
PersonalDashboardSchemaFlagsItem,
PersonalDashboardSchemaProjectsItem,
} from '../../openapi';
import { FlagExposure } from 'component/feature/FeatureView/FeatureOverview/FeatureLifecycle/FlagExposure';
import { usePersonalDashboardProjectDetails } from 'hooks/api/getters/usePersonalDashboard/usePersonalDashboardProjectDetails';
2024-09-30 13:32:05 +02:00
import HelpOutline from '@mui/icons-material/HelpOutline';
import useLoading from '../../hooks/useLoading';
import { MyProjects } from './MyProjects';
import {
ContentGridContainer,
FlagGrid,
ListItemBox,
listItemStyle,
GridItem,
SpacedGridItem,
} from './Grid';
import { ContentGridNoProjects } from './ContentGridNoProjects';
2024-09-19 12:37:35 +02:00
2024-09-30 13:32:05 +02:00
const ScreenExplanation = styled('div')(({ theme }) => ({
marginBottom: theme.spacing(4),
display: 'flex',
alignItems: 'center',
2024-09-19 12:37:35 +02:00
}));
export const StyledCardTitle = styled('div')<{ 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',
}),
);
const FlagListItem: FC<{
flag: { name: string; project: string; type: string };
selected: boolean;
onClick: () => void;
}> = ({ flag, selected, onClick }) => {
const activeFlagRef = useRef<HTMLLIElement>(null);
useEffect(() => {
if (activeFlagRef.current) {
activeFlagRef.current.scrollIntoView({
block: 'nearest',
inline: 'start',
});
}
}, []);
const IconComponent = getFeatureTypeIcons(flag.type);
return (
<ListItem
key={flag.name}
disablePadding={true}
sx={{ mb: 1 }}
ref={selected ? activeFlagRef : null}
>
<ListItemButton
sx={listItemStyle}
selected={selected}
onClick={onClick}
>
<ListItemBox>
<IconComponent color='primary' />
<StyledCardTitle>{flag.name}</StyledCardTitle>
<IconButton
component={Link}
href={`projects/${flag.project}/features/${flag.name}`}
size='small'
sx={{ ml: 'auto' }}
>
<LinkIcon
titleAccess={`projects/${flag.project}/features/${flag.name}`}
/>
</IconButton>
</ListItemBox>
</ListItemButton>
</ListItem>
);
};
const useDashboardState = (
projects: PersonalDashboardSchemaProjectsItem[],
flags: PersonalDashboardSchemaFlagsItem[],
) => {
type State = {
activeProject: string | undefined;
activeFlag: PersonalDashboardSchemaFlagsItem | undefined;
};
const defaultState = {
activeProject: undefined,
activeFlag: undefined,
};
const [state, setState] = useLocalStorageState<State>(
'personal-dashboard:v1',
defaultState,
);
useEffect(() => {
const setDefaultFlag =
flags.length &&
(!state.activeFlag ||
!flags.some((flag) => flag.name === state.activeFlag?.name));
const setDefaultProject =
projects.length &&
(!state.activeProject ||
!projects.some(
(project) => project.id === state.activeProject,
));
if (setDefaultFlag || setDefaultProject) {
setState({
activeFlag: setDefaultFlag ? flags[0] : state.activeFlag,
activeProject: setDefaultProject
? projects[0].id
: state.activeProject,
});
}
});
const { activeFlag, activeProject } = state;
const setActiveFlag = (flag: PersonalDashboardSchemaFlagsItem) => {
setState({
...state,
activeFlag: flag,
});
};
const setActiveProject = (projectId: string) => {
setState({
...state,
activeProject: projectId,
});
};
return {
activeFlag,
setActiveFlag,
activeProject,
setActiveProject,
};
};
export const PersonalDashboard = () => {
const { user } = useAuthUser();
const name = user?.name;
const {
personalDashboard,
refetch: refetchDashboard,
loading: personalDashboardLoading,
} = usePersonalDashboard();
const projects = personalDashboard?.projects || [];
const { activeProject, setActiveProject, activeFlag, setActiveFlag } =
useDashboardState(projects, personalDashboard?.flags ?? []);
const [welcomeDialog, setWelcomeDialog] = useLocalStorageState<
'open' | 'closed'
>('welcome-dialog:v1', 'open');
const {
personalDashboardProjectDetails,
loading: loadingDetails,
error: detailsError,
} = usePersonalDashboardProjectDetails(activeProject);
const activeProjectStage =
personalDashboardProjectDetails?.onboardingStatus.status ?? 'loading';
const setupIncomplete =
activeProjectStage === 'onboarding-started' ||
activeProjectStage === 'first-flag-created';
const noProjects = projects.length === 0;
const projectStageRef = useLoading(
!detailsError && activeProjectStage === 'loading',
);
2024-09-19 12:37:35 +02:00
return (
<div>
2024-09-19 12:37:35 +02:00
<Typography component='h2' variant='h2'>
Welcome {name}
</Typography>
<ScreenExplanation>
<p data-loading>
{activeProjectStage === 'onboarded'
? 'We have gathered projects and flags you have favorited or owned'
: null}
{setupIncomplete
? 'Here are some tasks we think would be useful in order to get the most out of Unleash'
: null}
{activeProjectStage === 'loading'
? 'We have gathered projects and flags you have favorited or owned'
: null}
2024-09-30 13:32:05 +02:00
</p>
<IconButton
data-loading
size={'small'}
title='Key concepts'
onClick={() => setWelcomeDialog('open')}
>
<HelpOutline />
</IconButton>
2024-09-19 12:37:35 +02:00
</ScreenExplanation>
2024-09-30 13:32:05 +02:00
{noProjects && personalDashboard ? (
<ContentGridNoProjects
owners={personalDashboard.projectOwners}
admins={personalDashboard.admins}
/>
) : (
<MyProjects
admins={personalDashboard?.admins ?? []}
ref={projectStageRef}
projects={projects}
activeProject={activeProject || ''}
setActiveProject={setActiveProject}
personalDashboardProjectDetails={
personalDashboardProjectDetails
}
/>
)}
<ContentGridContainer>
<FlagGrid sx={{ mt: 2 }}>
<GridItem
gridArea='title'
sx={{ display: 'flex', alignItems: 'center' }}
>
<Typography variant='h3'>My feature flags</Typography>
</GridItem>
<GridItem
gridArea='lifecycle'
sx={{ display: 'flex', justifyContent: 'flex-end' }}
>
{activeFlag ? (
<FlagExposure
project={activeFlag.project}
flagName={activeFlag.name}
onArchive={refetchDashboard}
/>
) : null}
</GridItem>
<SpacedGridItem gridArea='flags'>
{personalDashboard &&
personalDashboard.flags.length > 0 ? (
<List
disablePadding={true}
sx={{ maxHeight: '400px', overflow: 'auto' }}
>
{personalDashboard.flags.map((flag) => (
<FlagListItem
key={flag.name}
flag={flag}
selected={
flag.name === activeFlag?.name
}
onClick={() => setActiveFlag(flag)}
/>
))}
</List>
) : (
<Typography>
You have not created or favorited any feature
flags. Once you do, they will show up here.
</Typography>
)}
</SpacedGridItem>
<SpacedGridItem gridArea='chart'>
{activeFlag ? (
<FlagMetricsChart flag={activeFlag} />
) : (
<PlaceholderFlagMetricsChart />
)}
</SpacedGridItem>
</FlagGrid>
</ContentGridContainer>
<WelcomeDialog
2024-09-30 13:32:05 +02:00
open={welcomeDialog === 'open'}
onClose={() => setWelcomeDialog('closed')}
/>
2024-09-19 12:37:35 +02:00
</div>
);
2024-09-19 09:59:07 +02:00
};
const FlagMetricsChart = React.lazy(() =>
import('./FlagMetricsChart').then((module) => ({
default: module.FlagMetricsChart,
})),
);
const PlaceholderFlagMetricsChart = React.lazy(() =>
import('./FlagMetricsChart').then((module) => ({
default: module.PlaceholderFlagMetricsChart,
})),
);