Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: create page for when you have no projects #8285

Merged
merged 7 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do this transformation in three different places now, so I thought it would be about time to break it out into a shared component.

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import type { ProjectSchemaOwners } from 'openapi';
import type { UserAvatar } from '../UserAvatar/UserAvatar';
import { AvatarGroup } from '../AvatarGroup/AvatarGroup';

type Props = {
users: ProjectSchemaOwners;
avatarLimit?: number;
AvatarComponent?: typeof UserAvatar;
className?: string;
};
export const AvatarGroupFromOwners: React.FC<Props> = ({ users, ...props }) => {
const { uiConfig } = useUiConfig();

const mapOwners = (owner: ProjectSchemaOwners[number]) => {
if (owner.ownerType === 'user') {
return {
name: owner.name,
imageUrl: owner.imageUrl || undefined,
email: owner.email || undefined,
};
}
if (owner.ownerType === 'group') {
return {
name: owner.name,
};
}
return {
name: 'System',
imageUrl: `${uiConfig.unleashUrl}/logo-unleash.png`,
};
};
const mappedOwners = users.map(mapOwners);
return <AvatarGroup users={mappedOwners} {...props} />;
};
119 changes: 119 additions & 0 deletions frontend/src/component/personalDashboard/ContentGridNoProjects.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { Grid, Typography, styled } from '@mui/material';
import { AvatarGroupFromOwners } from 'component/common/AvatarGroupFromOwners/AvatarGroupFromOwners';
import useProjects from 'hooks/api/getters/useProjects/useProjects';
import type { ProjectSchemaOwners } from 'openapi';
import { Link } from 'react-router-dom';

const ContentGrid = styled(Grid)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
borderRadius: `${theme.shape.borderRadiusLarge}px`,
}));

const SpacedGridItem = styled(Grid)(({ theme }) => ({
padding: theme.spacing(4),
border: `0.5px solid ${theme.palette.divider}`,
}));

const TitleContainer = styled('div')(({ theme }) => ({
display: 'flex',
flexDirection: 'row',
gap: theme.spacing(2),
alignItems: 'center',
fontSize: theme.spacing(1.75),
fontWeight: 'bold',
}));

const NeutralCircleContainer = styled('span')(({ theme }) => ({
width: '28px',
height: '28px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: theme.palette.neutral.border,
borderRadius: '50%',
}));

const ActionBox = styled('div')(({ theme }) => ({
flexBasis: '50%',
padding: theme.spacing(4, 2),
display: 'flex',
gap: theme.spacing(3),
flexDirection: 'column',
}));

export const ContentGridNoProjects = () => {
const { projects } = useProjects();

const owners = projects.reduce(
(acc, project) => {
for (const owner of project.owners ?? []) {
if (owner.ownerType === 'user') {
acc[owner.email || owner.name] = owner;
}
}
return acc;
},
{} as Record<string, ProjectSchemaOwners[number]>,
);

return (
<ContentGrid container columns={{ lg: 12, md: 1 }}>
<SpacedGridItem item lg={4} md={1}>
<Typography variant='h3'>My projects</Typography>
</SpacedGridItem>
<SpacedGridItem
item
lg={8}
md={1}
sx={{ display: 'flex', justifyContent: 'flex-end' }}
>
<Typography>Potential next steps</Typography>
</SpacedGridItem>
<SpacedGridItem item lg={4} md={1}>
<ActionBox>
<Typography>
You don't currently have access to any projects in the
system.
</Typography>
<Typography>
To get started, you can{' '}
<Link to='/projects?create=true'>
create your own project
</Link>
. Alternatively, you can review the available projects
in the system and ask the owner for access.
</Typography>
</ActionBox>
</SpacedGridItem>
<SpacedGridItem item lg={4} md={1}>
<ActionBox>
<TitleContainer>
<NeutralCircleContainer>1</NeutralCircleContainer>
Contact Unleash admin
</TitleContainer>
<div>
<p>Your Unleash administrator is:</p>
<p>... someone, I guess? </p>
</div>
</ActionBox>
</SpacedGridItem>
<SpacedGridItem item lg={4} md={1}>
<ActionBox>
<TitleContainer>
<NeutralCircleContainer>2</NeutralCircleContainer>
Ask a project owner to add you to their project
</TitleContainer>
<div>
<p>Project owners in Unleash:</p>
<AvatarGroupFromOwners
users={Object.values(owners)}
avatarLimit={9}
/>
</div>
</ActionBox>
</SpacedGridItem>
<SpacedGridItem item lg={4} md={1} />
<SpacedGridItem item lg={8} md={1} />
</ContentGrid>
);
};
172 changes: 92 additions & 80 deletions frontend/src/component/personalDashboard/PersonalDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { getFeatureTypeIcons } from 'utils/getFeatureTypeIcons';
import type { PersonalDashboardSchema } from '../../openapi';
import { FlagExposure } from 'component/feature/FeatureView/FeatureOverview/FeatureLifecycle/FlagExposure';
import { RoleAndOwnerInfo } from './RoleAndOwnerInfo';
import { ContentGridNoProjects } from './ContentGridNoProjects';

const ScreenExplanation = styled(Typography)(({ theme }) => ({
marginTop: theme.spacing(1),
Expand Down Expand Up @@ -177,7 +178,12 @@ export const PersonalDashboard = () => {

const name = user?.name;

const { projects, activeProject, setActiveProject } = useProjects();
const {
// projects,
activeProject,
setActiveProject,
} = useProjects();
const projects = [];

const { personalDashboard, refetch: refetchDashboard } =
usePersonalDashboard();
Expand All @@ -203,6 +209,8 @@ export const PersonalDashboard = () => {
'seen' | 'not_seen'
>('welcome-dialog:v1', 'not_seen');

const noProjects = projects.length === 0;

return (
<div>
<Typography component='h2' variant='h2'>
Expand All @@ -213,88 +221,92 @@ export const PersonalDashboard = () => {
most of Unleash
</ScreenExplanation>
<StyledHeaderTitle>Your resources</StyledHeaderTitle>
<ContentGrid container columns={{ lg: 12, md: 1 }}>
<SpacedGridItem item lg={4} md={1}>
<Typography variant='h3'>My projects</Typography>
</SpacedGridItem>
<SpacedGridItem
item
lg={8}
md={1}
sx={{ display: 'flex', justifyContent: 'flex-end' }}
>
<Badge color='warning'>Setup incomplete</Badge>
</SpacedGridItem>
<SpacedGridItem item lg={4} md={1}>
<List
disablePadding={true}
sx={{ maxHeight: '400px', overflow: 'auto' }}
{noProjects ? (
<ContentGridNoProjects />
) : (
<ContentGrid container columns={{ lg: 12, md: 1 }}>
<SpacedGridItem item lg={4} md={1}>
<Typography variant='h3'>My projects</Typography>
</SpacedGridItem>
<SpacedGridItem
item
lg={8}
md={1}
sx={{ display: 'flex', justifyContent: 'flex-end' }}
>
{projects.map((project) => {
return (
<ListItem
key={project.name}
disablePadding={true}
sx={{ mb: 1 }}
>
<ListItemButton
sx={projectStyle}
selected={
project.name === activeProject
}
onClick={() =>
setActiveProject(project.name)
}
<Badge color='warning'>Setup incomplete</Badge>
</SpacedGridItem>
<SpacedGridItem item lg={4} md={1}>
<List
disablePadding={true}
sx={{ maxHeight: '400px', overflow: 'auto' }}
>
{projects.map((project) => {
return (
<ListItem
key={project.name}
disablePadding={true}
sx={{ mb: 1 }}
>
<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}`}
<ListItemButton
sx={projectStyle}
selected={
project.name === activeProject
}
onClick={() =>
setActiveProject(project.name)
}
>
<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}`}
/>
</IconButton>
</ProjectBox>
{project.name === activeProject ? (
<ActiveProjectDetails
project={project}
/>
</IconButton>
</ProjectBox>
{project.name === activeProject ? (
<ActiveProjectDetails
project={project}
/>
) : null}
</ListItemButton>
</ListItem>
);
})}
</List>
</SpacedGridItem>
<SpacedGridItem item lg={4} md={1}>
{onboardingCompleted ? (
<ProjectSetupComplete project={activeProject} />
) : activeProject ? (
<CreateFlag project={activeProject} />
) : null}
</SpacedGridItem>
<SpacedGridItem item lg={4} md={1}>
{activeProject ? (
<ConnectSDK project={activeProject} />
) : null}
</SpacedGridItem>
<SpacedGridItem item lg={4} md={1} />
<SpacedGridItem item lg={8} md={1}>
{activeProject ? (
<RoleAndOwnerInfo
roles={['owner', 'custom']}
owners={[{ ownerType: 'system' }]}
/>
) : null}
</SpacedGridItem>
</ContentGrid>
) : null}
</ListItemButton>
</ListItem>
);
})}
</List>
</SpacedGridItem>
<SpacedGridItem item lg={4} md={1}>
{onboardingCompleted ? (
<ProjectSetupComplete project={activeProject} />
) : activeProject ? (
<CreateFlag project={activeProject} />
) : null}
</SpacedGridItem>
<SpacedGridItem item lg={4} md={1}>
{activeProject ? (
<ConnectSDK project={activeProject} />
) : null}
</SpacedGridItem>
<SpacedGridItem item lg={4} md={1} />
<SpacedGridItem item lg={8} md={1}>
{activeProject ? (
<RoleAndOwnerInfo
roles={['owner', 'custom']}
owners={[{ ownerType: 'system' }]}
/>
) : null}
</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>
Expand Down
Loading
Loading