2024-09-19 12:37:35 +02:00
|
|
|
import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
|
|
|
|
import {
|
|
|
|
Box,
|
2024-09-20 11:05:53 +02:00
|
|
|
Grid,
|
2024-09-19 17:01:33 +02:00
|
|
|
IconButton,
|
|
|
|
Link,
|
2024-09-19 12:37:35 +02:00
|
|
|
List,
|
|
|
|
ListItem,
|
|
|
|
ListItemButton,
|
2024-09-19 17:01:33 +02:00
|
|
|
styled,
|
|
|
|
Typography,
|
2024-09-19 12:37:35 +02:00
|
|
|
} from '@mui/material';
|
|
|
|
import type { Theme } from '@mui/material/styles/createTheme';
|
|
|
|
import { ProjectIcon } from 'component/common/ProjectIcon/ProjectIcon';
|
2024-09-24 13:47:21 +02:00
|
|
|
import React, { type FC, useEffect, useState } from 'react';
|
2024-09-19 15:25:11 +02:00
|
|
|
import { useProfile } from 'hooks/api/getters/useProfile/useProfile';
|
|
|
|
import LinkIcon from '@mui/icons-material/Link';
|
2024-09-19 17:01:33 +02:00
|
|
|
import { Badge } from '../common/Badge/Badge';
|
|
|
|
import { ConnectSDK, CreateFlag } from './ConnectSDK';
|
2024-09-20 15:53:03 +02:00
|
|
|
import { WelcomeDialog } from './WelcomeDialog';
|
|
|
|
import { useLocalStorageState } from 'hooks/useLocalStorageState';
|
2024-09-23 14:23:22 +02:00
|
|
|
import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview';
|
|
|
|
import { ProjectSetupComplete } from './ProjectSetupComplete';
|
2024-09-24 08:42:49 +02:00
|
|
|
import { usePersonalDashboard } from 'hooks/api/getters/usePersonalDashboard/usePersonalDashboard';
|
|
|
|
import { getFeatureTypeIcons } from 'utils/getFeatureTypeIcons';
|
2024-09-24 13:47:21 +02:00
|
|
|
import type { PersonalDashboardSchema } from '../../openapi';
|
2024-09-19 12:37:35 +02:00
|
|
|
|
|
|
|
const ScreenExplanation = styled(Typography)(({ theme }) => ({
|
|
|
|
marginTop: theme.spacing(1),
|
|
|
|
marginBottom: theme.spacing(8),
|
|
|
|
maxWidth: theme.spacing(45),
|
|
|
|
}));
|
|
|
|
|
|
|
|
const StyledHeaderTitle = styled(Typography)(({ theme }) => ({
|
|
|
|
fontSize: theme.typography.h2.fontSize,
|
|
|
|
fontWeight: 'normal',
|
|
|
|
marginBottom: theme.spacing(2),
|
|
|
|
}));
|
|
|
|
|
2024-09-20 11:05:53 +02:00
|
|
|
const ContentGrid = styled(Grid)(({ theme }) => ({
|
2024-09-19 17:01:33 +02:00
|
|
|
backgroundColor: theme.palette.background.paper,
|
2024-09-19 12:37:35 +02:00
|
|
|
borderRadius: `${theme.shape.borderRadiusLarge}px`,
|
|
|
|
}));
|
|
|
|
|
2024-09-19 15:25:11 +02:00
|
|
|
const ProjectBox = styled(Box)(({ theme }) => ({
|
|
|
|
display: 'flex',
|
|
|
|
gap: theme.spacing(2),
|
|
|
|
alignItems: 'center',
|
|
|
|
width: '100%',
|
|
|
|
}));
|
|
|
|
|
2024-09-19 12:37:35 +02:00
|
|
|
const projectStyle = (theme: Theme) => ({
|
|
|
|
borderRadius: theme.spacing(0.5),
|
|
|
|
borderLeft: `${theme.spacing(0.5)} solid transparent`,
|
|
|
|
'&.Mui-selected': {
|
|
|
|
borderLeft: `${theme.spacing(0.5)} solid ${theme.palette.primary.main}`,
|
|
|
|
},
|
|
|
|
display: 'flex',
|
|
|
|
flexDirection: 'column',
|
|
|
|
alignItems: 'flex-start',
|
|
|
|
gap: theme.spacing(1),
|
|
|
|
});
|
|
|
|
|
2024-09-19 15:25:11 +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 ActiveProjectDetails: FC<{
|
|
|
|
project: { flags: number; members: number; health: number };
|
|
|
|
}> = ({ project }) => {
|
|
|
|
return (
|
|
|
|
<Box sx={{ display: 'flex', gap: 2 }}>
|
|
|
|
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
|
|
|
<Typography variant='subtitle2' color='primary'>
|
|
|
|
{project.flags}
|
|
|
|
</Typography>
|
|
|
|
<Typography variant='caption' color='text.secondary'>
|
|
|
|
flags
|
|
|
|
</Typography>
|
|
|
|
</Box>
|
|
|
|
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
|
|
|
<Typography variant='subtitle2' color='primary'>
|
|
|
|
{project.members}
|
|
|
|
</Typography>
|
|
|
|
<Typography variant='caption' color='text.secondary'>
|
|
|
|
members
|
|
|
|
</Typography>
|
|
|
|
</Box>
|
|
|
|
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
|
|
|
<Typography variant='subtitle2' color='primary'>
|
|
|
|
{project.health}%
|
|
|
|
</Typography>
|
|
|
|
<Typography variant='caption' color='text.secondary'>
|
|
|
|
health
|
|
|
|
</Typography>
|
|
|
|
</Box>
|
|
|
|
</Box>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2024-09-20 11:05:53 +02:00
|
|
|
const SpacedGridItem = styled(Grid)(({ theme }) => ({
|
2024-09-19 17:01:33 +02:00
|
|
|
padding: theme.spacing(4),
|
|
|
|
border: `0.5px solid ${theme.palette.divider}`,
|
|
|
|
}));
|
2024-09-19 12:37:35 +02:00
|
|
|
|
2024-09-19 17:01:33 +02:00
|
|
|
const useProjects = () => {
|
2024-09-19 15:25:11 +02:00
|
|
|
const myProjects = useProfile().profile?.projects || [];
|
|
|
|
|
2024-09-19 17:01:33 +02:00
|
|
|
// TODO: add real data for flags/members/health
|
2024-09-19 15:25:11 +02:00
|
|
|
const projects = myProjects.map((project) => ({
|
|
|
|
name: project,
|
|
|
|
flags: 0,
|
|
|
|
members: 1,
|
|
|
|
health: 100,
|
|
|
|
}));
|
|
|
|
|
2024-09-19 17:01:33 +02:00
|
|
|
const [activeProject, setActiveProject] = useState(projects[0]?.name);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (!activeProject && projects.length > 0) {
|
|
|
|
setActiveProject(projects[0].name);
|
|
|
|
}
|
|
|
|
}, [JSON.stringify(projects)]);
|
|
|
|
|
|
|
|
return { projects, activeProject, setActiveProject };
|
|
|
|
};
|
|
|
|
|
2024-09-24 08:42:49 +02:00
|
|
|
const FlagListItem: FC<{
|
|
|
|
flag: { name: string; project: string; type: string };
|
|
|
|
selected: boolean;
|
|
|
|
onClick: () => void;
|
|
|
|
}> = ({ flag, selected, onClick }) => {
|
|
|
|
const IconComponent = getFeatureTypeIcons(flag.type);
|
|
|
|
return (
|
|
|
|
<ListItem key={flag.name} disablePadding={true} sx={{ mb: 1 }}>
|
|
|
|
<ListItemButton
|
|
|
|
sx={projectStyle}
|
|
|
|
selected={selected}
|
|
|
|
onClick={onClick}
|
|
|
|
>
|
|
|
|
<ProjectBox>
|
|
|
|
<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>
|
|
|
|
</ProjectBox>
|
|
|
|
</ListItemButton>
|
|
|
|
</ListItem>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2024-09-19 17:01:33 +02:00
|
|
|
export const PersonalDashboard = () => {
|
|
|
|
const { user } = useAuthUser();
|
|
|
|
|
|
|
|
const name = user?.name;
|
|
|
|
|
|
|
|
const { projects, activeProject, setActiveProject } = useProjects();
|
2024-09-19 15:25:11 +02:00
|
|
|
|
2024-09-24 08:42:49 +02:00
|
|
|
const { personalDashboard } = usePersonalDashboard();
|
2024-09-24 13:47:21 +02:00
|
|
|
const [activeFlag, setActiveFlag] = useState<
|
|
|
|
PersonalDashboardSchema['flags'][0] | null
|
|
|
|
>(null);
|
2024-09-24 08:42:49 +02:00
|
|
|
useEffect(() => {
|
|
|
|
if (personalDashboard?.flags.length) {
|
2024-09-24 13:47:21 +02:00
|
|
|
setActiveFlag(personalDashboard.flags[0]);
|
2024-09-24 08:42:49 +02:00
|
|
|
}
|
|
|
|
}, [JSON.stringify(personalDashboard)]);
|
|
|
|
|
2024-09-23 14:23:22 +02:00
|
|
|
const { project: activeProjectOverview, loading } =
|
|
|
|
useProjectOverview(activeProject);
|
|
|
|
|
|
|
|
const onboardingCompleted = Boolean(
|
|
|
|
!loading &&
|
|
|
|
activeProject &&
|
|
|
|
activeProjectOverview?.onboardingStatus.status === 'onboarded',
|
|
|
|
);
|
|
|
|
|
2024-09-20 15:53:03 +02:00
|
|
|
const [welcomeDialog, setWelcomeDialog] = useLocalStorageState<
|
|
|
|
'seen' | 'not_seen'
|
|
|
|
>('welcome-dialog:v1', 'not_seen');
|
|
|
|
|
2024-09-19 12:37:35 +02:00
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
<Typography component='h2' variant='h2'>
|
|
|
|
Welcome {name}
|
|
|
|
</Typography>
|
|
|
|
<ScreenExplanation>
|
|
|
|
Here are some tasks we think would be useful in order to get the
|
|
|
|
most of Unleash
|
|
|
|
</ScreenExplanation>
|
|
|
|
<StyledHeaderTitle>Your resources</StyledHeaderTitle>
|
2024-09-20 11:05:53 +02:00
|
|
|
<ContentGrid container columns={{ lg: 12, md: 1 }}>
|
|
|
|
<SpacedGridItem item lg={4} md={1}>
|
2024-09-19 17:01:33 +02:00
|
|
|
<Typography variant='h3'>My projects</Typography>
|
2024-09-20 11:05:53 +02:00
|
|
|
</SpacedGridItem>
|
|
|
|
<SpacedGridItem
|
2024-09-19 17:01:33 +02:00
|
|
|
item
|
|
|
|
lg={8}
|
|
|
|
md={1}
|
|
|
|
sx={{ display: 'flex', justifyContent: 'flex-end' }}
|
|
|
|
>
|
|
|
|
<Badge color='warning'>Setup incomplete</Badge>
|
2024-09-20 11:05:53 +02:00
|
|
|
</SpacedGridItem>
|
|
|
|
<SpacedGridItem item lg={4} md={1}>
|
2024-09-19 17:01:33 +02:00
|
|
|
<List
|
|
|
|
disablePadding={true}
|
|
|
|
sx={{ maxHeight: '400px', overflow: 'auto' }}
|
|
|
|
>
|
|
|
|
{projects.map((project) => {
|
|
|
|
return (
|
|
|
|
<ListItem
|
|
|
|
key={project.name}
|
|
|
|
disablePadding={true}
|
|
|
|
sx={{ mb: 1 }}
|
|
|
|
>
|
|
|
|
<ListItemButton
|
|
|
|
sx={projectStyle}
|
|
|
|
selected={
|
|
|
|
project.name === activeProject
|
|
|
|
}
|
|
|
|
onClick={() =>
|
|
|
|
setActiveProject(project.name)
|
|
|
|
}
|
2024-09-19 15:25:11 +02:00
|
|
|
>
|
2024-09-19 17:01:33 +02:00
|
|
|
<ProjectBox>
|
|
|
|
<ProjectIcon color='primary' />
|
|
|
|
<StyledCardTitle>
|
|
|
|
{project.name}
|
|
|
|
</StyledCardTitle>
|
|
|
|
<IconButton
|
|
|
|
component={Link}
|
|
|
|
href={`projects/${project.name}`}
|
|
|
|
size='small'
|
|
|
|
sx={{ ml: 'auto' }}
|
|
|
|
>
|
|
|
|
<LinkIcon
|
|
|
|
titleAccess={`projects/${project.name}`}
|
2024-09-19 15:25:11 +02:00
|
|
|
/>
|
2024-09-19 17:01:33 +02:00
|
|
|
</IconButton>
|
|
|
|
</ProjectBox>
|
|
|
|
{project.name === activeProject ? (
|
|
|
|
<ActiveProjectDetails
|
|
|
|
project={project}
|
|
|
|
/>
|
|
|
|
) : null}
|
|
|
|
</ListItemButton>
|
|
|
|
</ListItem>
|
|
|
|
);
|
|
|
|
})}
|
|
|
|
</List>
|
2024-09-20 11:05:53 +02:00
|
|
|
</SpacedGridItem>
|
|
|
|
<SpacedGridItem item lg={4} md={1}>
|
2024-09-23 14:23:22 +02:00
|
|
|
{onboardingCompleted ? (
|
|
|
|
<ProjectSetupComplete project={activeProject} />
|
|
|
|
) : activeProject ? (
|
2024-09-19 17:01:33 +02:00
|
|
|
<CreateFlag project={activeProject} />
|
|
|
|
) : null}
|
2024-09-20 11:05:53 +02:00
|
|
|
</SpacedGridItem>
|
|
|
|
<SpacedGridItem item lg={4} md={1}>
|
2024-09-19 17:01:33 +02:00
|
|
|
{activeProject ? (
|
|
|
|
<ConnectSDK project={activeProject} />
|
|
|
|
) : null}
|
2024-09-20 11:05:53 +02:00
|
|
|
</SpacedGridItem>
|
|
|
|
<SpacedGridItem item lg={4} md={1} />
|
|
|
|
<SpacedGridItem
|
2024-09-19 17:01:33 +02:00
|
|
|
item
|
|
|
|
lg={8}
|
|
|
|
md={1}
|
|
|
|
sx={{ display: 'flex', gap: 1, alignItems: 'center' }}
|
|
|
|
>
|
|
|
|
<span>Your roles in this project:</span>{' '}
|
|
|
|
<Badge color='secondary'>Member</Badge>{' '}
|
|
|
|
<Badge color='secondary'>Another</Badge>
|
2024-09-20 11:05:53 +02:00
|
|
|
</SpacedGridItem>
|
|
|
|
</ContentGrid>
|
|
|
|
<ContentGrid container columns={{ lg: 12, md: 1 }} sx={{ mt: 2 }}>
|
|
|
|
<SpacedGridItem item lg={4} md={1}>
|
|
|
|
<Typography variant='h3'>My feature flags</Typography>
|
|
|
|
</SpacedGridItem>
|
|
|
|
<SpacedGridItem item lg={8} md={1} />
|
|
|
|
<SpacedGridItem item lg={4} md={1}>
|
2024-09-24 08:42:49 +02:00
|
|
|
{personalDashboard && personalDashboard.flags.length > 0 ? (
|
|
|
|
<List
|
|
|
|
disablePadding={true}
|
|
|
|
sx={{ maxHeight: '400px', overflow: 'auto' }}
|
|
|
|
>
|
|
|
|
{personalDashboard.flags.map((flag) => (
|
|
|
|
<FlagListItem
|
|
|
|
key={flag.name}
|
|
|
|
flag={flag}
|
2024-09-24 13:47:21 +02:00
|
|
|
selected={flag.name === activeFlag?.name}
|
|
|
|
onClick={() => setActiveFlag(flag)}
|
2024-09-24 08:42:49 +02:00
|
|
|
/>
|
|
|
|
))}
|
|
|
|
</List>
|
|
|
|
) : (
|
|
|
|
<Typography>
|
|
|
|
You have not created or favorited any feature flags.
|
|
|
|
Once you do, they will show up here.
|
|
|
|
</Typography>
|
|
|
|
)}
|
2024-09-20 11:05:53 +02:00
|
|
|
</SpacedGridItem>
|
2024-09-24 08:42:49 +02:00
|
|
|
|
2024-09-20 11:05:53 +02:00
|
|
|
<SpacedGridItem item lg={8} md={1}>
|
2024-09-24 13:47:21 +02:00
|
|
|
{activeFlag ? (
|
|
|
|
<FlagMetricsChart flag={activeFlag} />
|
|
|
|
) : (
|
|
|
|
<PlaceholderFlagMetricsChart />
|
|
|
|
)}
|
2024-09-20 11:05:53 +02:00
|
|
|
</SpacedGridItem>
|
|
|
|
</ContentGrid>
|
2024-09-20 15:53:03 +02:00
|
|
|
<WelcomeDialog
|
|
|
|
open={welcomeDialog !== 'seen'}
|
|
|
|
onClose={() => setWelcomeDialog('seen')}
|
|
|
|
/>
|
2024-09-19 12:37:35 +02:00
|
|
|
</div>
|
|
|
|
);
|
2024-09-19 09:59:07 +02:00
|
|
|
};
|
2024-09-24 13:47:21 +02:00
|
|
|
|
|
|
|
const FlagMetricsChart = React.lazy(() =>
|
|
|
|
import('./FlagMetricsChart').then((module) => ({
|
|
|
|
default: module.FlagMetricsChart,
|
|
|
|
})),
|
|
|
|
);
|
|
|
|
const PlaceholderFlagMetricsChart = React.lazy(() =>
|
|
|
|
import('./FlagMetricsChart').then((module) => ({
|
|
|
|
default: module.PlaceholderFlagMetricsChart,
|
|
|
|
})),
|
|
|
|
);
|