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:
parent
e6abaaccfb
commit
370daf8be4
35
frontend/src/component/personalDashboard/ActionBox.tsx
Normal file
35
frontend/src/component/personalDashboard/ActionBox.tsx
Normal 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>
|
||||
);
|
||||
};
|
@ -1,15 +1,8 @@
|
||||
import { Button, styled, Typography } from '@mui/material';
|
||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||
import type { FC } from 'react';
|
||||
|
||||
const TitleContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: theme.spacing(2),
|
||||
alignItems: 'center',
|
||||
fontSize: theme.spacing(1.75),
|
||||
fontWeight: 'bold',
|
||||
}));
|
||||
import { ActionBox } from './ActionBox';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const NeutralCircleContainer = styled('span')(({ theme }) => ({
|
||||
width: '28px',
|
||||
@ -37,22 +30,18 @@ const SuccessContainer = styled('div')(({ theme }) => ({
|
||||
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 }) => {
|
||||
const { trackEvent } = usePlausibleTracker();
|
||||
return (
|
||||
<ActionBox data-loading>
|
||||
<TitleContainer>
|
||||
<ActionBox
|
||||
data-loading
|
||||
title={
|
||||
<>
|
||||
<NeutralCircleContainer>1</NeutralCircleContainer>
|
||||
Create a feature flag
|
||||
</TitleContainer>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<p>The project currently holds no feature flags.</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 }) => {
|
||||
return (
|
||||
<ActionBox>
|
||||
<TitleContainer>
|
||||
<MainCircleContainer>✓</MainCircleContainer>
|
||||
<ActionBox
|
||||
title={
|
||||
<>
|
||||
<MainCircleContainer>1</MainCircleContainer>
|
||||
Create a feature flag
|
||||
</TitleContainer>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<SuccessContainer>
|
||||
<Typography fontWeight='bold' variant='body2'>
|
||||
You have created your first flag
|
||||
@ -92,7 +84,11 @@ export const ExistingFlag: FC<{ project: string }> = ({ project }) => {
|
||||
</Typography>
|
||||
</SuccessContainer>
|
||||
<div>
|
||||
<Button href={`projects/${project}`} variant='contained'>
|
||||
<Button
|
||||
component={Link}
|
||||
to={`/projects/${project}`}
|
||||
variant='contained'
|
||||
>
|
||||
Go to project
|
||||
</Button>
|
||||
</div>
|
||||
@ -102,11 +98,15 @@ export const ExistingFlag: FC<{ project: string }> = ({ project }) => {
|
||||
|
||||
export const ConnectSDK: FC<{ project: string }> = ({ project }) => {
|
||||
return (
|
||||
<ActionBox data-loading>
|
||||
<TitleContainer>
|
||||
<ActionBox
|
||||
data-loading
|
||||
title={
|
||||
<>
|
||||
<NeutralCircleContainer>2</NeutralCircleContainer>
|
||||
Connect an SDK
|
||||
</TitleContainer>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<p>Your project is not yet connected to any SDK.</p>
|
||||
<p>
|
||||
@ -115,7 +115,11 @@ export const ConnectSDK: FC<{ project: string }> = ({ project }) => {
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<Button href={`projects/${project}`} variant='contained'>
|
||||
<Button
|
||||
component={Link}
|
||||
to={`projects/${project}`}
|
||||
variant='contained'
|
||||
>
|
||||
Go to project
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -5,6 +5,7 @@ import { UserAvatar } from '../common/UserAvatar/UserAvatar';
|
||||
import { Typography, styled } from '@mui/material';
|
||||
import { formatDateYMDHM } from 'utils/formatDate';
|
||||
import { useLocationSettings } from 'hooks/useLocationSettings';
|
||||
import { ActionBox } from './ActionBox';
|
||||
|
||||
const Events = styled('ul')(({ theme }) => ({
|
||||
padding: 0,
|
||||
@ -33,13 +34,6 @@ const TitleContainer = styled('div')(({ theme }) => ({
|
||||
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 }) => ({
|
||||
color: theme.palette.text.secondary,
|
||||
fontSize: theme.typography.fontSize,
|
||||
@ -55,8 +49,8 @@ export const LatestProjectEvents: FC<{
|
||||
}> = ({ latestEvents }) => {
|
||||
const { locationSettings } = useLocationSettings();
|
||||
return (
|
||||
<ActionBox>
|
||||
<TitleContainer>
|
||||
<ActionBox
|
||||
title={
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 'bold',
|
||||
@ -65,7 +59,8 @@ export const LatestProjectEvents: FC<{
|
||||
>
|
||||
Latest events
|
||||
</Typography>
|
||||
</TitleContainer>
|
||||
}
|
||||
>
|
||||
<Events>
|
||||
{latestEvents.map((event) => {
|
||||
return (
|
||||
|
@ -1,7 +1,6 @@
|
||||
import {
|
||||
Box,
|
||||
IconButton,
|
||||
Link,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
Typography,
|
||||
@ -16,6 +15,7 @@ import { forwardRef, useEffect, useRef, type FC } from 'react';
|
||||
import type {
|
||||
PersonalDashboardProjectDetailsSchema,
|
||||
PersonalDashboardSchemaAdminsItem,
|
||||
PersonalDashboardSchemaProjectOwnersItem,
|
||||
PersonalDashboardSchemaProjectsItem,
|
||||
} from '../../openapi';
|
||||
import {
|
||||
@ -31,6 +31,8 @@ import {
|
||||
} from './SharedComponents';
|
||||
import { ContactAdmins, DataError } from './ProjectDetailsError';
|
||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ActionBox } from './ActionBox';
|
||||
|
||||
const ActiveProjectDetails: FC<{
|
||||
project: PersonalDashboardSchemaProjectsItem;
|
||||
@ -98,7 +100,7 @@ const ProjectListItem: FC<{
|
||||
<StyledCardTitle>{project.name}</StyledCardTitle>
|
||||
<IconButton
|
||||
component={Link}
|
||||
href={`projects/${project.id}`}
|
||||
to={`/projects/${project.id}`}
|
||||
size='small'
|
||||
sx={{ ml: 'auto' }}
|
||||
onClick={() => {
|
||||
@ -118,6 +120,8 @@ const ProjectListItem: FC<{
|
||||
);
|
||||
};
|
||||
|
||||
type MyProjectsState = 'no projects' | 'projects' | 'projects with error';
|
||||
|
||||
export const MyProjects = forwardRef<
|
||||
HTMLDivElement,
|
||||
{
|
||||
@ -126,6 +130,7 @@ export const MyProjects = forwardRef<
|
||||
activeProject: string;
|
||||
setActiveProject: (project: string) => void;
|
||||
admins: PersonalDashboardSchemaAdminsItem[];
|
||||
owners: PersonalDashboardSchemaProjectOwnersItem[];
|
||||
}
|
||||
>(
|
||||
(
|
||||
@ -138,6 +143,12 @@ export const MyProjects = forwardRef<
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const state: MyProjectsState = projects.length
|
||||
? personalDashboardProjectDetails
|
||||
? 'projects'
|
||||
: 'projects with error'
|
||||
: 'no projects';
|
||||
|
||||
const activeProjectStage =
|
||||
personalDashboardProjectDetails?.onboardingStatus.status ??
|
||||
'loading';
|
||||
@ -145,10 +156,11 @@ export const MyProjects = forwardRef<
|
||||
activeProjectStage === 'onboarding-started' ||
|
||||
activeProjectStage === 'first-flag-created';
|
||||
|
||||
const error = personalDashboardProjectDetails === undefined;
|
||||
|
||||
const box1Content = () => {
|
||||
if (error) {
|
||||
if (state === 'no projects') {
|
||||
}
|
||||
|
||||
if (state === 'projects with error') {
|
||||
return <DataError project={activeProject} />;
|
||||
}
|
||||
|
||||
@ -173,7 +185,7 @@ export const MyProjects = forwardRef<
|
||||
};
|
||||
|
||||
const box2Content = () => {
|
||||
if (error) {
|
||||
if (state === 'projects with error') {
|
||||
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 (
|
||||
<ContentGridContainer ref={ref}>
|
||||
<ProjectGrid>
|
||||
<SpacedGridItem gridArea='projects'>
|
||||
<StyledList>
|
||||
{projects.map((project) => (
|
||||
<ProjectListItem
|
||||
@ -209,6 +238,14 @@ export const MyProjects = forwardRef<
|
||||
/>
|
||||
))}
|
||||
</StyledList>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<ContentGridContainer ref={ref}>
|
||||
<ProjectGrid>
|
||||
<SpacedGridItem gridArea='projects'>
|
||||
{projectListContent()}
|
||||
</SpacedGridItem>
|
||||
<SpacedGridItem gridArea='box1'>
|
||||
{box1Content()}
|
||||
|
@ -1,32 +1,14 @@
|
||||
import { styled } from '@mui/material';
|
||||
import type { PersonalDashboardSchemaAdminsItem } from 'openapi';
|
||||
import type { FC } from 'react';
|
||||
import { YourAdmins } from './YourAdmins';
|
||||
|
||||
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',
|
||||
}));
|
||||
import { ActionBox } from './ActionBox';
|
||||
|
||||
export const DataError: FC<{ project: string }> = ({ project }) => {
|
||||
return (
|
||||
<ActionBox data-loading>
|
||||
<TitleContainer>
|
||||
Couldn't fetch data for project "{project}".
|
||||
</TitleContainer>
|
||||
|
||||
<ActionBox
|
||||
data-loading
|
||||
title={`Couldn't fetch data for project "${project}".`}
|
||||
>
|
||||
<p>
|
||||
The API request to get data for this project returned with an
|
||||
error.
|
||||
@ -44,10 +26,7 @@ export const ContactAdmins: FC<{
|
||||
admins: PersonalDashboardSchemaAdminsItem[];
|
||||
}> = ({ admins }) => {
|
||||
return (
|
||||
<ActionBox>
|
||||
<TitleContainer>
|
||||
Consider contacting one of your Unleash admins for help.
|
||||
</TitleContainer>
|
||||
<ActionBox title='Consider contacting one of your Unleash admins for help.'>
|
||||
<YourAdmins admins={admins} />
|
||||
</ActionBox>
|
||||
);
|
||||
|
@ -3,20 +3,7 @@ import type { FC } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Lightbulb from '@mui/icons-material/LightbulbOutlined';
|
||||
import type { PersonalDashboardProjectDetailsSchemaInsights } from '../../openapi';
|
||||
|
||||
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',
|
||||
}));
|
||||
import { ActionBox } from './ActionBox';
|
||||
|
||||
const PercentageScore = styled('span')(({ theme }) => ({
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
@ -145,14 +132,16 @@ export const ProjectSetupComplete: FC<{
|
||||
const projectHealthTrend = determineProjectHealthTrend(insights);
|
||||
|
||||
return (
|
||||
<ActionBox>
|
||||
<TitleContainer>
|
||||
<ActionBox
|
||||
title={
|
||||
<>
|
||||
<Lightbulb color='primary' />
|
||||
<Typography sx={{ fontWeight: 'bold' }} component='h4'>
|
||||
Project health
|
||||
</Typography>
|
||||
</TitleContainer>
|
||||
|
||||
</>
|
||||
}
|
||||
>
|
||||
<ProjectHealthMessage
|
||||
trend={projectHealthTrend}
|
||||
insights={insights}
|
||||
|
Loading…
Reference in New Issue
Block a user