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

fix: handle loading states for project details for a single project #8492

Merged
Merged
Show file tree
Hide file tree
Changes from 4 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
321 changes: 144 additions & 177 deletions frontend/src/component/personalDashboard/MyProjects.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import type { RemoteData } from './RemoteData';
import {
Box,
IconButton,
ListItem,
ListItemButton,
Typography,
styled,
} from '@mui/material';
import { ProjectIcon } from '../common/ProjectIcon/ProjectIcon';
import LinkIcon from '@mui/icons-material/ArrowForward';
import { ProjectSetupComplete } from './ProjectSetupComplete';
import { ConnectSDK, CreateFlag, ExistingFlag } from './ConnectSDK';
import { LatestProjectEvents } from './LatestProjectEvents';
import { RoleAndOwnerInfo } from './RoleAndOwnerInfo';
import { type ReactNode, forwardRef, useEffect, useRef, type FC } from 'react';
import { type ReactNode, useEffect, useRef, type FC } from 'react';
import type {
PersonalDashboardProjectDetailsSchema,
PersonalDashboardProjectDetailsSchemaRolesItem,
PersonalDashboardSchemaAdminsItem,
PersonalDashboardSchemaProjectOwnersItem,
PersonalDashboardSchemaProjectsItem,
Expand All @@ -33,6 +36,7 @@ import { ContactAdmins, DataError } from './ProjectDetailsError';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { Link } from 'react-router-dom';
import { ActionBox } from './ActionBox';
import useLoading from 'hooks/useLoading';
import { NoProjectsContactAdmin } from './NoProjectsContactAdmin';
import { AskOwnerToAddYouToTheirProject } from './AskOwnerToAddYouToTheirProject';

Expand Down Expand Up @@ -69,6 +73,10 @@ const ActiveProjectDetails: FC<{
);
};

const SkeletonDiv = styled('div')({
height: '80%',
});

const ProjectListItem: FC<{
project: PersonalDashboardSchemaProjectsItem;
selected: boolean;
Expand Down Expand Up @@ -122,186 +130,145 @@ const ProjectListItem: FC<{
);
};

type MyProjectsState = 'no projects' | 'projects' | 'projects with error';

export const MyProjects = forwardRef<
HTMLDivElement,
{
projects: PersonalDashboardSchemaProjectsItem[];
personalDashboardProjectDetails?: PersonalDashboardProjectDetailsSchema;
activeProject: string;
setActiveProject: (project: string) => void;
admins: PersonalDashboardSchemaAdminsItem[];
owners: PersonalDashboardSchemaProjectOwnersItem[];
}
>(
(
{
projects,
personalDashboardProjectDetails,
setActiveProject,
activeProject,
admins,
owners,
},
ref,
) => {
const state: MyProjectsState = projects.length
? personalDashboardProjectDetails
? 'projects'
: 'projects with error'
: 'no projects';

const activeProjectStage =
personalDashboardProjectDetails?.onboardingStatus.status ??
'loading';
const setupIncomplete =
activeProjectStage === 'onboarding-started' ||
activeProjectStage === 'first-flag-created';

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} />
),
};
export const MyProjects: React.FC<{
projects: PersonalDashboardSchemaProjectsItem[];
personalDashboardProjectDetails: RemoteData<PersonalDashboardProjectDetailsSchema>;
activeProject: string;
setActiveProject: (project: string) => void;
admins: PersonalDashboardSchemaAdminsItem[];
owners: PersonalDashboardSchemaProjectOwnersItem[];
}> = ({
projects,
personalDashboardProjectDetails,
setActiveProject,
activeProject,
admins,
owners,
}) => {
const ref = useLoading(personalDashboardProjectDetails.state === 'loading');

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} />,
};
const getGridContents = (): {
list: ReactNode;
box1: ReactNode;
box2: ReactNode;
} => {
if (projects.length === 0) {
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} />,
};
}

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} />;
}
})();
const list = (
<StyledList>
{projects.map((project) => (
<ProjectListItem
key={project.id}
project={project}
selected={project.id === activeProject}
onClick={() => setActiveProject(project.id)}
/>
))}
</StyledList>
);

const box2 = (() => {
if (
activeProjectStage === 'onboarded' &&
personalDashboardProjectDetails
) {
return (
<LatestProjectEvents
latestEvents={
personalDashboardProjectDetails.latestEvents
}
/>
);
} else if (
setupIncomplete ||
activeProjectStage === 'loading'
) {
return <ConnectSDK project={activeProject} />;
}
})();
const [box1, box2] = (() => {
switch (personalDashboardProjectDetails.state) {
case 'success': {
const activeProjectStage =
personalDashboardProjectDetails.data.onboardingStatus
.status ?? 'loading';
const setupIncomplete =
activeProjectStage === 'onboarding-started' ||
activeProjectStage === 'first-flag-created';

return {
list: (
<StyledList>
{projects.map((project) => (
<ProjectListItem
key={project.id}
project={project}
selected={project.id === activeProject}
onClick={() =>
setActiveProject(project.id)
}
/>
))}
</StyledList>
),
box1,
box2,
};
if (activeProjectStage === 'onboarded') {
return [
<ProjectSetupComplete
project={activeProject}
insights={
personalDashboardProjectDetails.data
.insights
}
/>,
<LatestProjectEvents
latestEvents={
personalDashboardProjectDetails.data
.latestEvents
}
/>,
];
} else if (setupIncomplete) {
return [
<CreateFlag project={activeProject} />,
<ConnectSDK project={activeProject} />,
];
} else {
return [
<ExistingFlag project={activeProject} />,
<ConnectSDK project={activeProject} />,
];
}
}
case 'error':
return [
<DataError project={activeProject} />,
<ContactAdmins admins={admins} />,
];
default: // loading
return [
<SkeletonDiv data-loading />,
<SkeletonDiv data-loading />,
];
}
};
})();

const { list, box1, box2 } = getGridContents();
return (
<ContentGridContainer ref={ref}>
<ProjectGrid>
<SpacedGridItem gridArea='projects'>{list}</SpacedGridItem>
<SpacedGridItem gridArea='box1'>{box1}</SpacedGridItem>
<SpacedGridItem gridArea='box2'>{box2}</SpacedGridItem>
<EmptyGridItem />
<GridItem gridArea='owners'>
<RoleAndOwnerInfo
roles={
personalDashboardProjectDetails?.roles.map(
(role) => role.name,
) ?? []
}
owners={
personalDashboardProjectDetails?.owners ?? [
{ ownerType: 'user', name: '?' },
]
}
/>
</GridItem>
</ProjectGrid>
</ContentGridContainer>
);
},
);
return { list, box1, box2 };
};

const { list, box1, box2 } = getGridContents();
return (
<ContentGridContainer ref={ref}>
<ProjectGrid>
<SpacedGridItem gridArea='projects'>{list}</SpacedGridItem>
<SpacedGridItem gridArea='box1'>{box1}</SpacedGridItem>
<SpacedGridItem gridArea='box2'>{box2}</SpacedGridItem>
<EmptyGridItem />
<GridItem gridArea='owners'>
<RoleAndOwnerInfo
roles={
personalDashboardProjectDetails.state === 'success'
? personalDashboardProjectDetails.data.roles.map(
(
role: PersonalDashboardProjectDetailsSchemaRolesItem,
) => role.name,
)
: []
}
owners={
personalDashboardProjectDetails.state === 'success'
? personalDashboardProjectDetails.data.owners
: [{ ownerType: 'user', name: '?' }]
}
/>
</GridItem>
</ProjectGrid>
</ContentGridContainer>
);
};
Loading
Loading