1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-10 01:16:39 +02:00

refactor: front end code pt II (#8444)

This PR continues the refactoring of the front end code for dashboards. 

The main points are:
- Extracts the `ActionBox` component that we used in a lot of places.
There were some minor differences between the various incarnations, so
this also better aligns them.
- Extract other components (`AskOwnerToAddYouToTheirProject`,
`YourAdmins`)
- Move the `NeutralCircleContainer` into `SharedComponents`
- Delete the separate no content grid (this is now handled in projects
instead)
- extract my projects grid contents into a single function so that it's
easier to understand what content you get for what states

Here's all the states side by side:

![image](https://github.com/user-attachments/assets/c5abc406-7374-41e4-8ff6-d48fe61cd7c8)
This commit is contained in:
Thomas Heartman 2024-10-15 11:14:24 +02:00 committed by GitHub
parent f5a2a18ffc
commit e4cfb29adc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 363 additions and 352 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

@ -0,0 +1,29 @@
import { ActionBox } from './ActionBox';
import { NeutralCircleContainer } from './SharedComponents';
import type { PersonalDashboardSchemaProjectOwnersItem } from 'openapi';
import type { FC } from 'react';
import { AvatarGroupFromOwners } from 'component/common/AvatarGroupFromOwners/AvatarGroupFromOwners';
export const AskOwnerToAddYouToTheirProject: FC<{
owners: PersonalDashboardSchemaProjectOwnersItem[];
}> = ({ owners }) => {
return (
<ActionBox
title={
<>
<NeutralCircleContainer>2</NeutralCircleContainer>
Ask a project owner to add you to their project
</>
}
>
{owners.length ? (
<>
<p>Project owners in Unleash:</p>
<AvatarGroupFromOwners users={owners} avatarLimit={9} />
</>
) : (
<p>There are no project owners in Unleash to ask for access.</p>
)}
</ActionBox>
);
};

View File

@ -1,25 +1,9 @@
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',
}));
const NeutralCircleContainer = styled('span')(({ theme }) => ({
width: '28px',
height: '28px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: theme.palette.neutral.border,
borderRadius: '50%',
}));
import { ActionBox } from './ActionBox';
import { Link } from 'react-router-dom';
import { NeutralCircleContainer } from './SharedComponents';
const MainCircleContainer = styled(NeutralCircleContainer)(({ theme }) => ({
backgroundColor: theme.palette.primary.main,
@ -37,22 +21,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>
<NeutralCircleContainer>1</NeutralCircleContainer>
Create a feature flag
</TitleContainer>
<ActionBox
data-loading
title={
<>
<NeutralCircleContainer>1</NeutralCircleContainer>
Create a feature flag
</>
}
>
<div>
<p>The project currently holds no feature flags.</p>
<p>Create one to get started.</p>
@ -78,11 +58,14 @@ export const CreateFlag: FC<{ project: string }> = ({ project }) => {
export const ExistingFlag: FC<{ project: string }> = ({ project }) => {
return (
<ActionBox>
<TitleContainer>
<MainCircleContainer></MainCircleContainer>
Create a feature flag
</TitleContainer>
<ActionBox
title={
<>
<MainCircleContainer>1</MainCircleContainer>
Create a feature flag
</>
}
>
<SuccessContainer>
<Typography fontWeight='bold' variant='body2'>
You have created your first flag
@ -92,7 +75,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 +89,15 @@ export const ExistingFlag: FC<{ project: string }> = ({ project }) => {
export const ConnectSDK: FC<{ project: string }> = ({ project }) => {
return (
<ActionBox data-loading>
<TitleContainer>
<NeutralCircleContainer>2</NeutralCircleContainer>
Connect an SDK
</TitleContainer>
<ActionBox
data-loading
title={
<>
<NeutralCircleContainer>2</NeutralCircleContainer>
Connect an SDK
</>
}
>
<div>
<p>Your project is not yet connected to any SDK.</p>
<p>
@ -115,7 +106,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>

View File

@ -1,165 +0,0 @@
import { Typography, styled } from '@mui/material';
import { AvatarGroupFromOwners } from 'component/common/AvatarGroupFromOwners/AvatarGroupFromOwners';
import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
import type { PersonalDashboardSchemaAdminsItem } from 'openapi/models/personalDashboardSchemaAdminsItem';
import type { PersonalDashboardSchemaProjectOwnersItem } from 'openapi/models/personalDashboardSchemaProjectOwnersItem';
import { Link } from 'react-router-dom';
import {
ContentGridContainer,
EmptyGridItem,
ProjectGrid,
GridItem,
} from './SharedComponents';
const PaddedEmptyGridItem = styled(EmptyGridItem)(({ theme }) => ({
padding: theme.spacing(4),
}));
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 GridContent = styled('div')(({ theme }) => ({
flexBasis: '50%',
padding: theme.spacing(4, 2),
display: 'flex',
gap: theme.spacing(3),
flexDirection: 'column',
}));
const BoxMainContent = styled('article')(({ theme }) => ({
display: 'flex',
flexFlow: 'column',
gap: theme.spacing(2),
}));
const AdminList = styled('ul')(({ theme }) => ({
padding: 0,
'li + li': {
marginTop: theme.spacing(2),
},
}));
const AdminListItem = styled('li')(({ theme }) => ({
display: 'flex',
flexFlow: 'row',
gap: theme.spacing(2),
}));
type Props = {
owners: PersonalDashboardSchemaProjectOwnersItem[];
admins: PersonalDashboardSchemaAdminsItem[];
};
export const AdminListRendered: React.FC<Pick<Props, 'admins'>> = ({
admins,
}) => {
return (
<BoxMainContent>
{admins.length ? (
<>
<p>
Your Unleash administrator
{admins.length > 1 ? 's are' : ' is'}:
</p>
<AdminList>
{admins.map((admin) => {
return (
<AdminListItem key={admin.id}>
<UserAvatar
sx={{
margin: 0,
}}
user={admin}
/>
<Typography>
{admin.name ||
admin.username ||
admin.email}
</Typography>
</AdminListItem>
);
})}
</AdminList>
</>
) : (
<p>You have no Unleash administrators to contact.</p>
)}
</BoxMainContent>
);
};
export const ContentGridNoProjects: React.FC<Props> = ({ owners, admins }) => {
return (
<ContentGridContainer>
<ProjectGrid>
<GridItem gridArea='projects'>
<GridContent>
<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>
</GridContent>
</GridItem>
<GridItem gridArea='box1'>
<GridContent>
<TitleContainer>
<NeutralCircleContainer>1</NeutralCircleContainer>
Contact Unleash admin
</TitleContainer>
<AdminListRendered admins={admins} />
</GridContent>
</GridItem>
<GridItem gridArea='box2'>
<GridContent>
<TitleContainer>
<NeutralCircleContainer>2</NeutralCircleContainer>
Ask a project owner to add you to their project
</TitleContainer>
<BoxMainContent>
{owners.length ? (
<>
<p>Project owners in Unleash:</p>
<AvatarGroupFromOwners
users={owners}
avatarLimit={9}
/>
</>
) : (
<p>
There are no project owners in Unleash to
ask for access.
</p>
)}
</BoxMainContent>
</GridContent>
</GridItem>
<EmptyGridItem />
<PaddedEmptyGridItem gridArea='owners' />
</ProjectGrid>
</ContentGridContainer>
);
};

View File

@ -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 (

View File

@ -1,7 +1,6 @@
import {
Box,
IconButton,
Link,
ListItem,
ListItemButton,
Typography,
@ -12,10 +11,11 @@ import { ProjectSetupComplete } from './ProjectSetupComplete';
import { ConnectSDK, CreateFlag, ExistingFlag } from './ConnectSDK';
import { LatestProjectEvents } from './LatestProjectEvents';
import { RoleAndOwnerInfo } from './RoleAndOwnerInfo';
import { forwardRef, useEffect, useRef, type FC } from 'react';
import { type ReactNode, forwardRef, useEffect, useRef, type FC } from 'react';
import type {
PersonalDashboardProjectDetailsSchema,
PersonalDashboardSchemaAdminsItem,
PersonalDashboardSchemaProjectOwnersItem,
PersonalDashboardSchemaProjectsItem,
} from '../../openapi';
import {
@ -31,6 +31,10 @@ import {
} from './SharedComponents';
import { ContactAdmins, DataError } from './ProjectDetailsError';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { Link } from 'react-router-dom';
import { ActionBox } from './ActionBox';
import { NoProjectsContactAdmin } from './NoProjectsContactAdmin';
import { AskOwnerToAddYouToTheirProject } from './AskOwnerToAddYouToTheirProject';
const ActiveProjectDetails: FC<{
project: PersonalDashboardSchemaProjectsItem;
@ -98,7 +102,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 +122,8 @@ const ProjectListItem: FC<{
);
};
type MyProjectsState = 'no projects' | 'projects' | 'projects with error';
export const MyProjects = forwardRef<
HTMLDivElement,
{
@ -126,6 +132,7 @@ export const MyProjects = forwardRef<
activeProject: string;
setActiveProject: (project: string) => void;
admins: PersonalDashboardSchemaAdminsItem[];
owners: PersonalDashboardSchemaProjectOwnersItem[];
}
>(
(
@ -135,9 +142,16 @@ export const MyProjects = forwardRef<
setActiveProject,
activeProject,
admins,
owners,
},
ref,
) => {
const state: MyProjectsState = projects.length
? personalDashboardProjectDetails
? 'projects'
: 'projects with error'
: 'no projects';
const activeProjectStage =
personalDashboardProjectDetails?.onboardingStatus.status ??
'loading';
@ -145,77 +159,132 @@ export const MyProjects = forwardRef<
activeProjectStage === 'onboarding-started' ||
activeProjectStage === 'first-flag-created';
const error = personalDashboardProjectDetails === undefined;
const getGridContents = (): {
list: ReactNode;
box1: ReactNode;
box2: ReactNode;
} => {
switch (state) {
case 'no projects':
return {
list: (
<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>
),
box1: <NoProjectsContactAdmin admins={admins} />,
box2: (
<AskOwnerToAddYouToTheirProject owners={owners} />
),
};
const box1Content = () => {
if (error) {
return <DataError project={activeProject} />;
}
case 'projects with error':
return {
list: (
<StyledList>
{projects.map((project) => (
<ProjectListItem
key={project.id}
project={project}
selected={project.id === activeProject}
onClick={() =>
setActiveProject(project.id)
}
/>
))}
</StyledList>
),
box1: <DataError project={activeProject} />,
box2: <ContactAdmins admins={admins} />,
};
if (
activeProjectStage === 'onboarded' &&
personalDashboardProjectDetails
) {
return (
<ProjectSetupComplete
project={activeProject}
insights={personalDashboardProjectDetails.insights}
/>
);
} else if (
activeProjectStage === 'onboarding-started' ||
activeProjectStage === 'loading'
) {
return <CreateFlag project={activeProject} />;
} else if (activeProjectStage === 'first-flag-created') {
return <ExistingFlag project={activeProject} />;
}
};
const box2Content = () => {
if (error) {
return <ContactAdmins admins={admins} />;
}
if (
activeProjectStage === 'onboarded' &&
personalDashboardProjectDetails
) {
return (
<LatestProjectEvents
latestEvents={
personalDashboardProjectDetails.latestEvents
case 'projects': {
const box1 = (() => {
if (
activeProjectStage === 'onboarded' &&
personalDashboardProjectDetails
) {
return (
<ProjectSetupComplete
project={activeProject}
insights={
personalDashboardProjectDetails.insights
}
/>
);
} else if (
activeProjectStage === 'onboarding-started' ||
activeProjectStage === 'loading'
) {
return <CreateFlag project={activeProject} />;
} else if (
activeProjectStage === 'first-flag-created'
) {
return <ExistingFlag project={activeProject} />;
}
/>
);
}
})();
if (setupIncomplete || activeProjectStage === 'loading') {
return <ConnectSDK project={activeProject} />;
const box2 = (() => {
if (
activeProjectStage === 'onboarded' &&
personalDashboardProjectDetails
) {
return (
<LatestProjectEvents
latestEvents={
personalDashboardProjectDetails.latestEvents
}
/>
);
} else if (
setupIncomplete ||
activeProjectStage === 'loading'
) {
return <ConnectSDK project={activeProject} />;
}
})();
return {
list: (
<StyledList>
{projects.map((project) => (
<ProjectListItem
key={project.id}
project={project}
selected={project.id === activeProject}
onClick={() =>
setActiveProject(project.id)
}
/>
))}
</StyledList>
),
box1,
box2,
};
}
}
};
const { list, box1, box2 } = getGridContents();
return (
<ContentGridContainer ref={ref}>
<ProjectGrid>
<SpacedGridItem gridArea='projects'>
<StyledList>
{projects.map((project) => (
<ProjectListItem
key={project.id}
project={project}
selected={project.id === activeProject}
onClick={() => setActiveProject(project.id)}
/>
))}
</StyledList>
</SpacedGridItem>
<SpacedGridItem gridArea='box1'>
{box1Content()}
</SpacedGridItem>
<SpacedGridItem gridArea='box2'>
{box2Content()}
</SpacedGridItem>
<SpacedGridItem gridArea='projects'>{list}</SpacedGridItem>
<SpacedGridItem gridArea='box1'>{box1}</SpacedGridItem>
<SpacedGridItem gridArea='box2'>{box2}</SpacedGridItem>
<EmptyGridItem />
<GridItem gridArea='owners'>
<RoleAndOwnerInfo

View File

@ -0,0 +1,22 @@
import { ActionBox } from './ActionBox';
import { YourAdmins } from './YourAdmins';
import { NeutralCircleContainer } from './SharedComponents';
import type { PersonalDashboardSchemaAdminsItem } from 'openapi';
import type { FC } from 'react';
export const NoProjectsContactAdmin: FC<{
admins: PersonalDashboardSchemaAdminsItem[];
}> = ({ admins }) => {
return (
<ActionBox
title={
<>
<NeutralCircleContainer>1</NeutralCircleContainer>
Contact Unleash admin
</>
}
>
<YourAdmins admins={admins} />
</ActionBox>
);
};

View File

@ -13,7 +13,6 @@ import { usePersonalDashboard } from 'hooks/api/getters/usePersonalDashboard/use
import { usePersonalDashboardProjectDetails } from 'hooks/api/getters/usePersonalDashboard/usePersonalDashboardProjectDetails';
import useLoading from '../../hooks/useLoading';
import { MyProjects } from './MyProjects';
import { ContentGridNoProjects } from './ContentGridNoProjects';
import ExpandMore from '@mui/icons-material/ExpandMore';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import useSplashApi from 'hooks/api/actions/useSplashApi/useSplashApi';
@ -165,23 +164,17 @@ export const PersonalDashboard = () => {
</Typography>
</StyledAccordionSummary>
<StyledAccordionDetails>
{noProjects && personalDashboard ? (
<ContentGridNoProjects
owners={personalDashboard.projectOwners}
admins={personalDashboard.admins}
/>
) : (
<MyProjects
admins={personalDashboard?.admins ?? []}
ref={projectStageRef}
projects={projects}
activeProject={activeProject || ''}
setActiveProject={setActiveProject}
personalDashboardProjectDetails={
personalDashboardProjectDetails
}
/>
)}
<MyProjects
owners={personalDashboard?.projectOwners ?? []}
admins={personalDashboard?.admins ?? []}
ref={projectStageRef}
projects={projects}
activeProject={activeProject || ''}
setActiveProject={setActiveProject}
personalDashboardProjectDetails={
personalDashboardProjectDetails
}
/>
</StyledAccordionDetails>
</SectionAccordion>

View File

@ -1,32 +1,14 @@
import { styled } from '@mui/material';
import type { PersonalDashboardSchemaAdminsItem } from 'openapi';
import type { FC } from 'react';
import { AdminListRendered } from './ContentGridNoProjects';
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 { YourAdmins } from './YourAdmins';
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,11 +26,8 @@ export const ContactAdmins: FC<{
admins: PersonalDashboardSchemaAdminsItem[];
}> = ({ admins }) => {
return (
<ActionBox>
<TitleContainer>
Consider contacting one of your Unleash admins for help.
</TitleContainer>
<AdminListRendered admins={admins} />
<ActionBox title='Consider contacting one of your Unleash admins for help.'>
<YourAdmins admins={admins} />
</ActionBox>
);
};

View File

@ -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>
<Lightbulb color='primary' />
<Typography sx={{ fontWeight: 'bold' }} component='h4'>
Project health
</Typography>
</TitleContainer>
<ActionBox
title={
<>
<Lightbulb color='primary' />
<Typography sx={{ fontWeight: 'bold' }} component='h4'>
Project health
</Typography>
</>
}
>
<ProjectHealthMessage
trend={projectHealthTrend}
insights={insights}

View File

@ -130,3 +130,13 @@ export const StyledCardTitle = styled('div')<{ lines?: number }>(
wordBreak: 'break-word',
}),
);
export const NeutralCircleContainer = styled('span')(({ theme }) => ({
width: '28px',
height: '28px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: theme.palette.neutral.border,
borderRadius: '50%',
}));

View File

@ -0,0 +1,60 @@
import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
import { Typography, styled } from '@mui/material';
import type { PersonalDashboardSchemaAdminsItem } from 'openapi';
const StyledList = styled('ul')(({ theme }) => ({
padding: 0,
'li + li': {
marginTop: theme.spacing(2),
},
}));
const StyledListItem = styled('li')(({ theme }) => ({
display: 'flex',
flexFlow: 'row',
gap: theme.spacing(2),
}));
const Wrapper = styled('article')(({ theme }) => ({
display: 'flex',
flexFlow: 'column',
gap: theme.spacing(2),
}));
export const YourAdmins: React.FC<{
admins: PersonalDashboardSchemaAdminsItem[];
}> = ({ admins }) => {
return (
<Wrapper>
{admins.length ? (
<>
<p>
Your Unleash administrator
{admins.length > 1 ? 's are' : ' is'}:
</p>
<StyledList>
{admins.map((admin) => {
return (
<StyledListItem key={admin.id}>
<UserAvatar
sx={{
margin: 0,
}}
user={admin}
/>
<Typography>
{admin.name ||
admin.username ||
admin.email}
</Typography>
</StyledListItem>
);
})}
</StyledList>
</>
) : (
<p>You have no Unleash administrators to contact.</p>
)}
</Wrapper>
);
};