1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-09-19 17:52:45 +02:00

refactor: extract ActionBox component

This commit is contained in:
Thomas Heartman 2024-10-14 14:54:21 +02:00
parent e6abaaccfb
commit 370daf8be4
No known key found for this signature in database
GPG Key ID: BD1F880DAED1EE78
6 changed files with 148 additions and 109 deletions

View File

@ -0,0 +1,35 @@
import { styled } from '@mui/material';
import type { FC, PropsWithChildren, ReactNode } from 'react';
const Container = styled('article')(({ theme }) => ({
padding: theme.spacing(4, 2),
display: 'flex',
gap: theme.spacing(3),
flexDirection: 'column',
}));
const TitleContainer = styled('div')(({ theme }) => ({
display: 'flex',
flexDirection: 'row',
gap: theme.spacing(2),
alignItems: 'center',
fontSize: theme.spacing(1.75),
fontWeight: 'bold',
}));
type Props = {
title?: string | ReactNode;
};
export const ActionBox: FC<PropsWithChildren<Props>> = ({
title,
children,
}) => {
return (
<Container>
{title ? <TitleContainer>{title}</TitleContainer> : null}
{children}
</Container>
);
};

View File

@ -1,15 +1,8 @@
import { Button, styled, Typography } from '@mui/material'; import { Button, styled, Typography } from '@mui/material';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import type { FC } from 'react'; import type { FC } from 'react';
import { ActionBox } from './ActionBox';
const TitleContainer = styled('div')(({ theme }) => ({ import { Link } from 'react-router-dom';
display: 'flex',
flexDirection: 'row',
gap: theme.spacing(2),
alignItems: 'center',
fontSize: theme.spacing(1.75),
fontWeight: 'bold',
}));
const NeutralCircleContainer = styled('span')(({ theme }) => ({ const NeutralCircleContainer = styled('span')(({ theme }) => ({
width: '28px', width: '28px',
@ -37,22 +30,18 @@ const SuccessContainer = styled('div')(({ theme }) => ({
padding: theme.spacing(2, 2, 2, 2), padding: theme.spacing(2, 2, 2, 2),
})); }));
const ActionBox = styled('div')(({ theme }) => ({
flexBasis: '50%',
padding: theme.spacing(4, 2),
display: 'flex',
gap: theme.spacing(3),
flexDirection: 'column',
}));
export const CreateFlag: FC<{ project: string }> = ({ project }) => { export const CreateFlag: FC<{ project: string }> = ({ project }) => {
const { trackEvent } = usePlausibleTracker(); const { trackEvent } = usePlausibleTracker();
return ( return (
<ActionBox data-loading> <ActionBox
<TitleContainer> data-loading
title={
<>
<NeutralCircleContainer>1</NeutralCircleContainer> <NeutralCircleContainer>1</NeutralCircleContainer>
Create a feature flag Create a feature flag
</TitleContainer> </>
}
>
<div> <div>
<p>The project currently holds no feature flags.</p> <p>The project currently holds no feature flags.</p>
<p>Create one to get started.</p> <p>Create one to get started.</p>
@ -78,11 +67,14 @@ export const CreateFlag: FC<{ project: string }> = ({ project }) => {
export const ExistingFlag: FC<{ project: string }> = ({ project }) => { export const ExistingFlag: FC<{ project: string }> = ({ project }) => {
return ( return (
<ActionBox> <ActionBox
<TitleContainer> title={
<MainCircleContainer></MainCircleContainer> <>
<MainCircleContainer>1</MainCircleContainer>
Create a feature flag Create a feature flag
</TitleContainer> </>
}
>
<SuccessContainer> <SuccessContainer>
<Typography fontWeight='bold' variant='body2'> <Typography fontWeight='bold' variant='body2'>
You have created your first flag You have created your first flag
@ -92,7 +84,11 @@ export const ExistingFlag: FC<{ project: string }> = ({ project }) => {
</Typography> </Typography>
</SuccessContainer> </SuccessContainer>
<div> <div>
<Button href={`projects/${project}`} variant='contained'> <Button
component={Link}
to={`/projects/${project}`}
variant='contained'
>
Go to project Go to project
</Button> </Button>
</div> </div>
@ -102,11 +98,15 @@ export const ExistingFlag: FC<{ project: string }> = ({ project }) => {
export const ConnectSDK: FC<{ project: string }> = ({ project }) => { export const ConnectSDK: FC<{ project: string }> = ({ project }) => {
return ( return (
<ActionBox data-loading> <ActionBox
<TitleContainer> data-loading
title={
<>
<NeutralCircleContainer>2</NeutralCircleContainer> <NeutralCircleContainer>2</NeutralCircleContainer>
Connect an SDK Connect an SDK
</TitleContainer> </>
}
>
<div> <div>
<p>Your project is not yet connected to any SDK.</p> <p>Your project is not yet connected to any SDK.</p>
<p> <p>
@ -115,7 +115,11 @@ export const ConnectSDK: FC<{ project: string }> = ({ project }) => {
</p> </p>
</div> </div>
<div> <div>
<Button href={`projects/${project}`} variant='contained'> <Button
component={Link}
to={`projects/${project}`}
variant='contained'
>
Go to project Go to project
</Button> </Button>
</div> </div>

View File

@ -5,6 +5,7 @@ import { UserAvatar } from '../common/UserAvatar/UserAvatar';
import { Typography, styled } from '@mui/material'; import { Typography, styled } from '@mui/material';
import { formatDateYMDHM } from 'utils/formatDate'; import { formatDateYMDHM } from 'utils/formatDate';
import { useLocationSettings } from 'hooks/useLocationSettings'; import { useLocationSettings } from 'hooks/useLocationSettings';
import { ActionBox } from './ActionBox';
const Events = styled('ul')(({ theme }) => ({ const Events = styled('ul')(({ theme }) => ({
padding: 0, padding: 0,
@ -33,13 +34,6 @@ const TitleContainer = styled('div')(({ theme }) => ({
alignItems: 'center', alignItems: 'center',
})); }));
const ActionBox = styled('article')(({ theme }) => ({
padding: theme.spacing(0, 2),
display: 'flex',
gap: theme.spacing(3),
flexDirection: 'column',
}));
const Timestamp = styled('time')(({ theme }) => ({ const Timestamp = styled('time')(({ theme }) => ({
color: theme.palette.text.secondary, color: theme.palette.text.secondary,
fontSize: theme.typography.fontSize, fontSize: theme.typography.fontSize,
@ -55,8 +49,8 @@ export const LatestProjectEvents: FC<{
}> = ({ latestEvents }) => { }> = ({ latestEvents }) => {
const { locationSettings } = useLocationSettings(); const { locationSettings } = useLocationSettings();
return ( return (
<ActionBox> <ActionBox
<TitleContainer> title={
<Typography <Typography
sx={{ sx={{
fontWeight: 'bold', fontWeight: 'bold',
@ -65,7 +59,8 @@ export const LatestProjectEvents: FC<{
> >
Latest events Latest events
</Typography> </Typography>
</TitleContainer> }
>
<Events> <Events>
{latestEvents.map((event) => { {latestEvents.map((event) => {
return ( return (

View File

@ -1,7 +1,6 @@
import { import {
Box, Box,
IconButton, IconButton,
Link,
ListItem, ListItem,
ListItemButton, ListItemButton,
Typography, Typography,
@ -16,6 +15,7 @@ import { forwardRef, useEffect, useRef, type FC } from 'react';
import type { import type {
PersonalDashboardProjectDetailsSchema, PersonalDashboardProjectDetailsSchema,
PersonalDashboardSchemaAdminsItem, PersonalDashboardSchemaAdminsItem,
PersonalDashboardSchemaProjectOwnersItem,
PersonalDashboardSchemaProjectsItem, PersonalDashboardSchemaProjectsItem,
} from '../../openapi'; } from '../../openapi';
import { import {
@ -31,6 +31,8 @@ import {
} from './SharedComponents'; } from './SharedComponents';
import { ContactAdmins, DataError } from './ProjectDetailsError'; import { ContactAdmins, DataError } from './ProjectDetailsError';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { Link } from 'react-router-dom';
import { ActionBox } from './ActionBox';
const ActiveProjectDetails: FC<{ const ActiveProjectDetails: FC<{
project: PersonalDashboardSchemaProjectsItem; project: PersonalDashboardSchemaProjectsItem;
@ -98,7 +100,7 @@ const ProjectListItem: FC<{
<StyledCardTitle>{project.name}</StyledCardTitle> <StyledCardTitle>{project.name}</StyledCardTitle>
<IconButton <IconButton
component={Link} component={Link}
href={`projects/${project.id}`} to={`/projects/${project.id}`}
size='small' size='small'
sx={{ ml: 'auto' }} sx={{ ml: 'auto' }}
onClick={() => { onClick={() => {
@ -118,6 +120,8 @@ const ProjectListItem: FC<{
); );
}; };
type MyProjectsState = 'no projects' | 'projects' | 'projects with error';
export const MyProjects = forwardRef< export const MyProjects = forwardRef<
HTMLDivElement, HTMLDivElement,
{ {
@ -126,6 +130,7 @@ export const MyProjects = forwardRef<
activeProject: string; activeProject: string;
setActiveProject: (project: string) => void; setActiveProject: (project: string) => void;
admins: PersonalDashboardSchemaAdminsItem[]; admins: PersonalDashboardSchemaAdminsItem[];
owners: PersonalDashboardSchemaProjectOwnersItem[];
} }
>( >(
( (
@ -138,6 +143,12 @@ export const MyProjects = forwardRef<
}, },
ref, ref,
) => { ) => {
const state: MyProjectsState = projects.length
? personalDashboardProjectDetails
? 'projects'
: 'projects with error'
: 'no projects';
const activeProjectStage = const activeProjectStage =
personalDashboardProjectDetails?.onboardingStatus.status ?? personalDashboardProjectDetails?.onboardingStatus.status ??
'loading'; 'loading';
@ -145,10 +156,11 @@ export const MyProjects = forwardRef<
activeProjectStage === 'onboarding-started' || activeProjectStage === 'onboarding-started' ||
activeProjectStage === 'first-flag-created'; activeProjectStage === 'first-flag-created';
const error = personalDashboardProjectDetails === undefined;
const box1Content = () => { const box1Content = () => {
if (error) { if (state === 'no projects') {
}
if (state === 'projects with error') {
return <DataError project={activeProject} />; return <DataError project={activeProject} />;
} }
@ -173,7 +185,7 @@ export const MyProjects = forwardRef<
}; };
const box2Content = () => { const box2Content = () => {
if (error) { if (state === 'projects with error') {
return <ContactAdmins admins={admins} />; return <ContactAdmins admins={admins} />;
} }
@ -195,10 +207,27 @@ export const MyProjects = forwardRef<
} }
}; };
const projectListContent = () => {
if (state === 'no projects') {
return (
<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>
);
}
return ( return (
<ContentGridContainer ref={ref}>
<ProjectGrid>
<SpacedGridItem gridArea='projects'>
<StyledList> <StyledList>
{projects.map((project) => ( {projects.map((project) => (
<ProjectListItem <ProjectListItem
@ -209,6 +238,14 @@ export const MyProjects = forwardRef<
/> />
))} ))}
</StyledList> </StyledList>
);
};
return (
<ContentGridContainer ref={ref}>
<ProjectGrid>
<SpacedGridItem gridArea='projects'>
{projectListContent()}
</SpacedGridItem> </SpacedGridItem>
<SpacedGridItem gridArea='box1'> <SpacedGridItem gridArea='box1'>
{box1Content()} {box1Content()}

View File

@ -1,32 +1,14 @@
import { styled } from '@mui/material';
import type { PersonalDashboardSchemaAdminsItem } from 'openapi'; import type { PersonalDashboardSchemaAdminsItem } from 'openapi';
import type { FC } from 'react'; import type { FC } from 'react';
import { YourAdmins } from './YourAdmins'; import { YourAdmins } from './YourAdmins';
import { ActionBox } from './ActionBox';
const TitleContainer = styled('div')(({ theme }) => ({
display: 'flex',
flexDirection: 'row',
gap: theme.spacing(2),
alignItems: 'center',
fontSize: theme.spacing(1.75),
fontWeight: 'bold',
}));
const ActionBox = styled('div')(({ theme }) => ({
flexBasis: '50%',
padding: theme.spacing(4, 2),
display: 'flex',
gap: theme.spacing(3),
flexDirection: 'column',
}));
export const DataError: FC<{ project: string }> = ({ project }) => { export const DataError: FC<{ project: string }> = ({ project }) => {
return ( return (
<ActionBox data-loading> <ActionBox
<TitleContainer> data-loading
Couldn't fetch data for project "{project}". title={`Couldn't fetch data for project "${project}".`}
</TitleContainer> >
<p> <p>
The API request to get data for this project returned with an The API request to get data for this project returned with an
error. error.
@ -44,10 +26,7 @@ export const ContactAdmins: FC<{
admins: PersonalDashboardSchemaAdminsItem[]; admins: PersonalDashboardSchemaAdminsItem[];
}> = ({ admins }) => { }> = ({ admins }) => {
return ( return (
<ActionBox> <ActionBox title='Consider contacting one of your Unleash admins for help.'>
<TitleContainer>
Consider contacting one of your Unleash admins for help.
</TitleContainer>
<YourAdmins admins={admins} /> <YourAdmins admins={admins} />
</ActionBox> </ActionBox>
); );

View File

@ -3,20 +3,7 @@ import type { FC } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import Lightbulb from '@mui/icons-material/LightbulbOutlined'; import Lightbulb from '@mui/icons-material/LightbulbOutlined';
import type { PersonalDashboardProjectDetailsSchemaInsights } from '../../openapi'; import type { PersonalDashboardProjectDetailsSchemaInsights } from '../../openapi';
import { ActionBox } from './ActionBox';
const TitleContainer = styled('div')(({ theme }) => ({
display: 'flex',
flexDirection: 'row',
gap: theme.spacing(2),
alignItems: 'center',
}));
const ActionBox = styled('article')(({ theme }) => ({
padding: theme.spacing(0, 2),
display: 'flex',
gap: theme.spacing(3),
flexDirection: 'column',
}));
const PercentageScore = styled('span')(({ theme }) => ({ const PercentageScore = styled('span')(({ theme }) => ({
fontWeight: theme.typography.fontWeightBold, fontWeight: theme.typography.fontWeightBold,
@ -145,14 +132,16 @@ export const ProjectSetupComplete: FC<{
const projectHealthTrend = determineProjectHealthTrend(insights); const projectHealthTrend = determineProjectHealthTrend(insights);
return ( return (
<ActionBox> <ActionBox
<TitleContainer> title={
<>
<Lightbulb color='primary' /> <Lightbulb color='primary' />
<Typography sx={{ fontWeight: 'bold' }} component='h4'> <Typography sx={{ fontWeight: 'bold' }} component='h4'>
Project health Project health
</Typography> </Typography>
</TitleContainer> </>
}
>
<ProjectHealthMessage <ProjectHealthMessage
trend={projectHealthTrend} trend={projectHealthTrend}
insights={insights} insights={insights}