mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-04 00:18:01 +01:00
refactor: refactor personal dashboard front end code pt1 (#8440)
This is the first step in refactoring the front end code for personal dashboards. At this point: - extract `useDashboardState` to its own file - extract my flags to its own file - Rename `Grid.tsx` to `SharedComponents.tsx` as it contains more than just the grid.
This commit is contained in:
parent
39fb1b5db5
commit
9d49070cee
@ -9,7 +9,7 @@ import {
|
|||||||
EmptyGridItem,
|
EmptyGridItem,
|
||||||
ProjectGrid,
|
ProjectGrid,
|
||||||
GridItem,
|
GridItem,
|
||||||
} from './Grid';
|
} from './SharedComponents';
|
||||||
|
|
||||||
const PaddedEmptyGridItem = styled(EmptyGridItem)(({ theme }) => ({
|
const PaddedEmptyGridItem = styled(EmptyGridItem)(({ theme }) => ({
|
||||||
padding: theme.spacing(4),
|
padding: theme.spacing(4),
|
||||||
|
180
frontend/src/component/personalDashboard/MyFlags.tsx
Normal file
180
frontend/src/component/personalDashboard/MyFlags.tsx
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
import { type FC, useEffect, useRef } from 'react';
|
||||||
|
import {
|
||||||
|
ContentGridContainer,
|
||||||
|
FlagGrid,
|
||||||
|
ListItemBox,
|
||||||
|
SpacedGridItem,
|
||||||
|
StyledCardTitle,
|
||||||
|
StyledList,
|
||||||
|
listItemStyle,
|
||||||
|
} from './SharedComponents';
|
||||||
|
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||||
|
import { getFeatureTypeIcons } from 'utils/getFeatureTypeIcons';
|
||||||
|
import {
|
||||||
|
Alert,
|
||||||
|
IconButton,
|
||||||
|
Link,
|
||||||
|
ListItem,
|
||||||
|
ListItemButton,
|
||||||
|
Typography,
|
||||||
|
styled,
|
||||||
|
} from '@mui/material';
|
||||||
|
import LinkIcon from '@mui/icons-material/ArrowForward';
|
||||||
|
import React from 'react';
|
||||||
|
import type { PersonalDashboardSchemaFlagsItem } from 'openapi';
|
||||||
|
|
||||||
|
const NoActiveFlagsInfo = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexFlow: 'column',
|
||||||
|
gap: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const FlagListItem: FC<{
|
||||||
|
flag: { name: string; project: string; type: string };
|
||||||
|
selected: boolean;
|
||||||
|
onClick: () => void;
|
||||||
|
}> = ({ flag, selected, onClick }) => {
|
||||||
|
const activeFlagRef = useRef<HTMLLIElement>(null);
|
||||||
|
const { trackEvent } = usePlausibleTracker();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (activeFlagRef.current) {
|
||||||
|
activeFlagRef.current.scrollIntoView({
|
||||||
|
block: 'nearest',
|
||||||
|
inline: 'start',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
const IconComponent = getFeatureTypeIcons(flag.type);
|
||||||
|
const flagLink = `projects/${flag.project}/features/${flag.name}`;
|
||||||
|
return (
|
||||||
|
<ListItem
|
||||||
|
key={flag.name}
|
||||||
|
disablePadding={true}
|
||||||
|
sx={{ mb: 1 }}
|
||||||
|
ref={selected ? activeFlagRef : null}
|
||||||
|
>
|
||||||
|
<ListItemButton
|
||||||
|
sx={listItemStyle}
|
||||||
|
selected={selected}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<ListItemBox>
|
||||||
|
<IconComponent color='primary' />
|
||||||
|
<StyledCardTitle>{flag.name}</StyledCardTitle>
|
||||||
|
<IconButton
|
||||||
|
component={Link}
|
||||||
|
href={flagLink}
|
||||||
|
onClick={() => {
|
||||||
|
trackEvent('personal-dashboard', {
|
||||||
|
props: {
|
||||||
|
eventType: `Go to flag from list`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
size='small'
|
||||||
|
sx={{ ml: 'auto' }}
|
||||||
|
>
|
||||||
|
<LinkIcon titleAccess={flagLink} />
|
||||||
|
</IconButton>
|
||||||
|
</ListItemBox>
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type FlagData =
|
||||||
|
| {
|
||||||
|
state: 'flags';
|
||||||
|
flags: PersonalDashboardSchemaFlagsItem[];
|
||||||
|
activeFlag?: PersonalDashboardSchemaFlagsItem;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
state: 'no flags';
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
hasProjects: boolean;
|
||||||
|
flagData: FlagData;
|
||||||
|
setActiveFlag: (flag: PersonalDashboardSchemaFlagsItem) => void;
|
||||||
|
refetchDashboard: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MyFlags: FC<Props> = ({
|
||||||
|
hasProjects,
|
||||||
|
flagData,
|
||||||
|
setActiveFlag,
|
||||||
|
refetchDashboard,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<ContentGridContainer>
|
||||||
|
<FlagGrid>
|
||||||
|
<SpacedGridItem gridArea='flags'>
|
||||||
|
{flagData.state === 'flags' ? (
|
||||||
|
<StyledList
|
||||||
|
disablePadding={true}
|
||||||
|
sx={{
|
||||||
|
height: '100%',
|
||||||
|
overflow: 'auto',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{flagData.flags.map((flag) => (
|
||||||
|
<FlagListItem
|
||||||
|
key={flag.name}
|
||||||
|
flag={flag}
|
||||||
|
selected={
|
||||||
|
flag.name === flagData.activeFlag?.name
|
||||||
|
}
|
||||||
|
onClick={() => setActiveFlag(flag)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</StyledList>
|
||||||
|
) : hasProjects ? (
|
||||||
|
<NoActiveFlagsInfo>
|
||||||
|
<Typography>
|
||||||
|
You have not created or favorited any feature
|
||||||
|
flags. Once you do, they will show up here.
|
||||||
|
</Typography>
|
||||||
|
<Typography>
|
||||||
|
To create a new flag, go to one of your
|
||||||
|
projects.
|
||||||
|
</Typography>
|
||||||
|
</NoActiveFlagsInfo>
|
||||||
|
) : (
|
||||||
|
<Alert severity='info'>
|
||||||
|
You need to create or join a project to be able to
|
||||||
|
add a flag, or you must be given the rights by your
|
||||||
|
admin to add feature flags.
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
</SpacedGridItem>
|
||||||
|
|
||||||
|
<SpacedGridItem gridArea='chart'>
|
||||||
|
{flagData.state === 'flags' && flagData.activeFlag ? (
|
||||||
|
<FlagMetricsChart
|
||||||
|
flag={flagData.activeFlag}
|
||||||
|
onArchive={refetchDashboard}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<PlaceholderFlagMetricsChart
|
||||||
|
label={
|
||||||
|
'Metrics for your feature flags will be shown here'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</SpacedGridItem>
|
||||||
|
</FlagGrid>
|
||||||
|
</ContentGridContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const FlagMetricsChart = React.lazy(() =>
|
||||||
|
import('./FlagMetricsChart').then((module) => ({
|
||||||
|
default: module.FlagMetricsChart,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
const PlaceholderFlagMetricsChart = React.lazy(() =>
|
||||||
|
import('./FlagMetricsChart').then((module) => ({
|
||||||
|
default: module.PlaceholderFlagMetricsChartWithWrapper,
|
||||||
|
})),
|
||||||
|
);
|
@ -13,7 +13,6 @@ import { ConnectSDK, CreateFlag, ExistingFlag } from './ConnectSDK';
|
|||||||
import { LatestProjectEvents } from './LatestProjectEvents';
|
import { LatestProjectEvents } from './LatestProjectEvents';
|
||||||
import { RoleAndOwnerInfo } from './RoleAndOwnerInfo';
|
import { RoleAndOwnerInfo } from './RoleAndOwnerInfo';
|
||||||
import { forwardRef, useEffect, useRef, type FC } from 'react';
|
import { forwardRef, useEffect, useRef, type FC } from 'react';
|
||||||
import { StyledCardTitle } from './PersonalDashboard';
|
|
||||||
import type {
|
import type {
|
||||||
PersonalDashboardProjectDetailsSchema,
|
PersonalDashboardProjectDetailsSchema,
|
||||||
PersonalDashboardSchemaAdminsItem,
|
PersonalDashboardSchemaAdminsItem,
|
||||||
@ -28,7 +27,8 @@ import {
|
|||||||
GridItem,
|
GridItem,
|
||||||
SpacedGridItem,
|
SpacedGridItem,
|
||||||
StyledList,
|
StyledList,
|
||||||
} from './Grid';
|
StyledCardTitle,
|
||||||
|
} from './SharedComponents';
|
||||||
import { ContactAdmins, DataError } from './ProjectDetailsError';
|
import { ContactAdmins, DataError } from './ProjectDetailsError';
|
||||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||||
|
|
||||||
|
@ -3,201 +3,23 @@ import {
|
|||||||
Accordion,
|
Accordion,
|
||||||
AccordionDetails,
|
AccordionDetails,
|
||||||
AccordionSummary,
|
AccordionSummary,
|
||||||
Alert,
|
|
||||||
Button,
|
Button,
|
||||||
IconButton,
|
|
||||||
Link,
|
|
||||||
ListItem,
|
|
||||||
ListItemButton,
|
|
||||||
styled,
|
styled,
|
||||||
Typography,
|
Typography,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import React, { type FC, useEffect, useRef } from 'react';
|
|
||||||
import LinkIcon from '@mui/icons-material/ArrowForward';
|
|
||||||
import { WelcomeDialog } from './WelcomeDialog';
|
import { WelcomeDialog } from './WelcomeDialog';
|
||||||
import { useLocalStorageState } from 'hooks/useLocalStorageState';
|
import { useLocalStorageState } from 'hooks/useLocalStorageState';
|
||||||
import { usePersonalDashboard } from 'hooks/api/getters/usePersonalDashboard/usePersonalDashboard';
|
import { usePersonalDashboard } from 'hooks/api/getters/usePersonalDashboard/usePersonalDashboard';
|
||||||
import { getFeatureTypeIcons } from 'utils/getFeatureTypeIcons';
|
|
||||||
import type {
|
|
||||||
PersonalDashboardSchemaFlagsItem,
|
|
||||||
PersonalDashboardSchemaProjectsItem,
|
|
||||||
} from '../../openapi';
|
|
||||||
import { usePersonalDashboardProjectDetails } from 'hooks/api/getters/usePersonalDashboard/usePersonalDashboardProjectDetails';
|
import { usePersonalDashboardProjectDetails } from 'hooks/api/getters/usePersonalDashboard/usePersonalDashboardProjectDetails';
|
||||||
import useLoading from '../../hooks/useLoading';
|
import useLoading from '../../hooks/useLoading';
|
||||||
import { MyProjects } from './MyProjects';
|
import { MyProjects } from './MyProjects';
|
||||||
import {
|
|
||||||
ContentGridContainer,
|
|
||||||
FlagGrid,
|
|
||||||
ListItemBox,
|
|
||||||
listItemStyle,
|
|
||||||
SpacedGridItem,
|
|
||||||
StyledList,
|
|
||||||
} from './Grid';
|
|
||||||
import { ContentGridNoProjects } from './ContentGridNoProjects';
|
import { ContentGridNoProjects } from './ContentGridNoProjects';
|
||||||
import ExpandMore from '@mui/icons-material/ExpandMore';
|
import ExpandMore from '@mui/icons-material/ExpandMore';
|
||||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||||
import useSplashApi from 'hooks/api/actions/useSplashApi/useSplashApi';
|
import useSplashApi from 'hooks/api/actions/useSplashApi/useSplashApi';
|
||||||
import { useAuthSplash } from 'hooks/api/getters/useAuth/useAuthSplash';
|
import { useAuthSplash } from 'hooks/api/getters/useAuth/useAuthSplash';
|
||||||
|
import { useDashboardState } from './useDashboardState';
|
||||||
export const StyledCardTitle = styled('div')<{ lines?: number }>(
|
import { MyFlags } from './MyFlags';
|
||||||
({ theme, lines = 2 }) => ({
|
|
||||||
fontWeight: theme.typography.fontWeightRegular,
|
|
||||||
fontSize: theme.typography.body1.fontSize,
|
|
||||||
lineClamp: `${lines}`,
|
|
||||||
WebkitLineClamp: lines,
|
|
||||||
lineHeight: '1.2',
|
|
||||||
display: '-webkit-box',
|
|
||||||
boxOrient: 'vertical',
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
overflow: 'hidden',
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
WebkitBoxOrient: 'vertical',
|
|
||||||
wordBreak: 'break-word',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
const FlagListItem: FC<{
|
|
||||||
flag: { name: string; project: string; type: string };
|
|
||||||
selected: boolean;
|
|
||||||
onClick: () => void;
|
|
||||||
}> = ({ flag, selected, onClick }) => {
|
|
||||||
const activeFlagRef = useRef<HTMLLIElement>(null);
|
|
||||||
const { trackEvent } = usePlausibleTracker();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (activeFlagRef.current) {
|
|
||||||
activeFlagRef.current.scrollIntoView({
|
|
||||||
block: 'nearest',
|
|
||||||
inline: 'start',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
const IconComponent = getFeatureTypeIcons(flag.type);
|
|
||||||
const flagLink = `projects/${flag.project}/features/${flag.name}`;
|
|
||||||
return (
|
|
||||||
<ListItem
|
|
||||||
key={flag.name}
|
|
||||||
disablePadding={true}
|
|
||||||
sx={{ mb: 1 }}
|
|
||||||
ref={selected ? activeFlagRef : null}
|
|
||||||
>
|
|
||||||
<ListItemButton
|
|
||||||
sx={listItemStyle}
|
|
||||||
selected={selected}
|
|
||||||
onClick={onClick}
|
|
||||||
>
|
|
||||||
<ListItemBox>
|
|
||||||
<IconComponent color='primary' />
|
|
||||||
<StyledCardTitle>{flag.name}</StyledCardTitle>
|
|
||||||
<IconButton
|
|
||||||
component={Link}
|
|
||||||
href={flagLink}
|
|
||||||
onClick={() => {
|
|
||||||
trackEvent('personal-dashboard', {
|
|
||||||
props: {
|
|
||||||
eventType: `Go to flag from list`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
size='small'
|
|
||||||
sx={{ ml: 'auto' }}
|
|
||||||
>
|
|
||||||
<LinkIcon titleAccess={flagLink} />
|
|
||||||
</IconButton>
|
|
||||||
</ListItemBox>
|
|
||||||
</ListItemButton>
|
|
||||||
</ListItem>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// todo: move into own file
|
|
||||||
const useDashboardState = (
|
|
||||||
projects: PersonalDashboardSchemaProjectsItem[],
|
|
||||||
flags: PersonalDashboardSchemaFlagsItem[],
|
|
||||||
) => {
|
|
||||||
type State = {
|
|
||||||
activeProject: string | undefined;
|
|
||||||
activeFlag: PersonalDashboardSchemaFlagsItem | undefined;
|
|
||||||
expandProjects: boolean;
|
|
||||||
expandFlags: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultState: State = {
|
|
||||||
activeProject: undefined,
|
|
||||||
activeFlag: undefined,
|
|
||||||
expandProjects: true,
|
|
||||||
expandFlags: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const [state, setState] = useLocalStorageState<State>(
|
|
||||||
'personal-dashboard:v1',
|
|
||||||
defaultState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const updateState = (newState: Partial<State>) =>
|
|
||||||
setState({ ...defaultState, ...state, ...newState });
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const updates: Partial<State> = {};
|
|
||||||
const setDefaultFlag =
|
|
||||||
flags.length &&
|
|
||||||
(!state.activeFlag ||
|
|
||||||
!flags.some((flag) => flag.name === state.activeFlag?.name));
|
|
||||||
|
|
||||||
if (setDefaultFlag) {
|
|
||||||
updates.activeFlag = flags[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
const setDefaultProject =
|
|
||||||
projects.length &&
|
|
||||||
(!state.activeProject ||
|
|
||||||
!projects.some(
|
|
||||||
(project) => project.id === state.activeProject,
|
|
||||||
));
|
|
||||||
|
|
||||||
if (setDefaultProject) {
|
|
||||||
updates.activeProject = projects[0].id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Object.keys(updates).length) {
|
|
||||||
updateState(updates);
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
JSON.stringify(projects, null, 2),
|
|
||||||
JSON.stringify(flags, null, 2),
|
|
||||||
JSON.stringify(state, null, 2),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const { activeFlag, activeProject } = state;
|
|
||||||
|
|
||||||
const setActiveFlag = (flag: PersonalDashboardSchemaFlagsItem) => {
|
|
||||||
updateState({
|
|
||||||
activeFlag: flag,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const setActiveProject = (projectId: string) => {
|
|
||||||
updateState({
|
|
||||||
activeProject: projectId,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleSectionState = (section: 'flags' | 'projects') => {
|
|
||||||
const property = section === 'flags' ? 'expandFlags' : 'expandProjects';
|
|
||||||
updateState({
|
|
||||||
[property]: !(state[property] ?? true),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
activeFlag,
|
|
||||||
setActiveFlag,
|
|
||||||
activeProject,
|
|
||||||
setActiveProject,
|
|
||||||
expandFlags: state.expandFlags ?? true,
|
|
||||||
expandProjects: state.expandProjects ?? true,
|
|
||||||
toggleSectionState,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const WelcomeSection = styled('div')(({ theme }) => ({
|
const WelcomeSection = styled('div')(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -258,12 +80,6 @@ const MainContent = styled('div')(({ theme }) => ({
|
|||||||
gap: theme.spacing(2),
|
gap: theme.spacing(2),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const NoActiveFlagsInfo = styled('div')(({ theme }) => ({
|
|
||||||
display: 'flex',
|
|
||||||
flexFlow: 'column',
|
|
||||||
gap: theme.spacing(2),
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const PersonalDashboard = () => {
|
export const PersonalDashboard = () => {
|
||||||
const { user } = useAuthUser();
|
const { user } = useAuthUser();
|
||||||
const { trackEvent } = usePlausibleTracker();
|
const { trackEvent } = usePlausibleTracker();
|
||||||
@ -385,70 +201,20 @@ export const PersonalDashboard = () => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</StyledAccordionSummary>
|
</StyledAccordionSummary>
|
||||||
<StyledAccordionDetails>
|
<StyledAccordionDetails>
|
||||||
<ContentGridContainer>
|
<MyFlags
|
||||||
<FlagGrid>
|
hasProjects={projects?.length > 0}
|
||||||
<SpacedGridItem gridArea='flags'>
|
flagData={
|
||||||
{personalDashboard &&
|
personalDashboard && personalDashboard.flags.length
|
||||||
personalDashboard.flags.length > 0 ? (
|
? {
|
||||||
<StyledList
|
state: 'flags' as const,
|
||||||
disablePadding={true}
|
activeFlag,
|
||||||
sx={{
|
flags: personalDashboard.flags,
|
||||||
height: '100%',
|
}
|
||||||
overflow: 'auto',
|
: { state: 'no flags' as const }
|
||||||
}}
|
}
|
||||||
>
|
setActiveFlag={setActiveFlag}
|
||||||
{personalDashboard.flags.map((flag) => (
|
refetchDashboard={refetchDashboard}
|
||||||
<FlagListItem
|
/>
|
||||||
key={flag.name}
|
|
||||||
flag={flag}
|
|
||||||
selected={
|
|
||||||
flag.name ===
|
|
||||||
activeFlag?.name
|
|
||||||
}
|
|
||||||
onClick={() =>
|
|
||||||
setActiveFlag(flag)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</StyledList>
|
|
||||||
) : activeProject ? (
|
|
||||||
<NoActiveFlagsInfo>
|
|
||||||
<Typography>
|
|
||||||
You have not created or favorited
|
|
||||||
any feature flags. Once you do, they
|
|
||||||
will show up here.
|
|
||||||
</Typography>
|
|
||||||
<Typography>
|
|
||||||
To create a new flag, go to one of
|
|
||||||
your projects.
|
|
||||||
</Typography>
|
|
||||||
</NoActiveFlagsInfo>
|
|
||||||
) : (
|
|
||||||
<Alert severity='info'>
|
|
||||||
You need to create or join a project to
|
|
||||||
be able to add a flag, or you must be
|
|
||||||
given the rights by your admin to add
|
|
||||||
feature flags.
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
</SpacedGridItem>
|
|
||||||
|
|
||||||
<SpacedGridItem gridArea='chart'>
|
|
||||||
{activeFlag ? (
|
|
||||||
<FlagMetricsChart
|
|
||||||
flag={activeFlag}
|
|
||||||
onArchive={refetchDashboard}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<PlaceholderFlagMetricsChart
|
|
||||||
label={
|
|
||||||
'Metrics for your feature flags will be shown here'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</SpacedGridItem>
|
|
||||||
</FlagGrid>
|
|
||||||
</ContentGridContainer>
|
|
||||||
</StyledAccordionDetails>
|
</StyledAccordionDetails>
|
||||||
</SectionAccordion>
|
</SectionAccordion>
|
||||||
<WelcomeDialog
|
<WelcomeDialog
|
||||||
@ -461,14 +227,3 @@ export const PersonalDashboard = () => {
|
|||||||
</MainContent>
|
</MainContent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const FlagMetricsChart = React.lazy(() =>
|
|
||||||
import('./FlagMetricsChart').then((module) => ({
|
|
||||||
default: module.FlagMetricsChart,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
const PlaceholderFlagMetricsChart = React.lazy(() =>
|
|
||||||
import('./FlagMetricsChart').then((module) => ({
|
|
||||||
default: module.PlaceholderFlagMetricsChartWithWrapper,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
|
@ -58,7 +58,7 @@ export const FlagGrid = styled(ContentGrid)(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const GridItem = styled('div', {
|
export const GridItem = styled('div', {
|
||||||
shouldForwardProp: (prop) => !['gridArea', 'sx'].includes(prop.toString()),
|
shouldForwardProp: (prop) => !['gridArea'].includes(prop.toString()),
|
||||||
})<{ gridArea: string }>(({ theme, gridArea }) => ({
|
})<{ gridArea: string }>(({ theme, gridArea }) => ({
|
||||||
padding: theme.spacing(2, 4),
|
padding: theme.spacing(2, 4),
|
||||||
maxHeight: '100%',
|
maxHeight: '100%',
|
||||||
@ -113,3 +113,20 @@ export const StyledList = styled(List)(({ theme }) => ({
|
|||||||
maxHeight: '100%',
|
maxHeight: '100%',
|
||||||
})({ theme }),
|
})({ theme }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
export const StyledCardTitle = styled('div')<{ lines?: number }>(
|
||||||
|
({ theme, lines = 2 }) => ({
|
||||||
|
fontWeight: theme.typography.fontWeightRegular,
|
||||||
|
fontSize: theme.typography.body1.fontSize,
|
||||||
|
lineClamp: `${lines}`,
|
||||||
|
WebkitLineClamp: lines,
|
||||||
|
lineHeight: '1.2',
|
||||||
|
display: '-webkit-box',
|
||||||
|
boxOrient: 'vertical',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
overflow: 'hidden',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
WebkitBoxOrient: 'vertical',
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
}),
|
||||||
|
);
|
@ -0,0 +1,95 @@
|
|||||||
|
import { useLocalStorageState } from 'hooks/useLocalStorageState';
|
||||||
|
import type {
|
||||||
|
PersonalDashboardSchemaFlagsItem,
|
||||||
|
PersonalDashboardSchemaProjectsItem,
|
||||||
|
} from 'openapi';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
export const useDashboardState = (
|
||||||
|
projects: PersonalDashboardSchemaProjectsItem[],
|
||||||
|
flags: PersonalDashboardSchemaFlagsItem[],
|
||||||
|
) => {
|
||||||
|
type State = {
|
||||||
|
activeProject: string | undefined;
|
||||||
|
activeFlag: PersonalDashboardSchemaFlagsItem | undefined;
|
||||||
|
expandProjects: boolean;
|
||||||
|
expandFlags: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultState: State = {
|
||||||
|
activeProject: undefined,
|
||||||
|
activeFlag: undefined,
|
||||||
|
expandProjects: true,
|
||||||
|
expandFlags: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const [state, setState] = useLocalStorageState<State>(
|
||||||
|
'personal-dashboard:v1',
|
||||||
|
defaultState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateState = (newState: Partial<State>) =>
|
||||||
|
setState({ ...defaultState, ...state, ...newState });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const updates: Partial<State> = {};
|
||||||
|
const setDefaultFlag =
|
||||||
|
flags.length &&
|
||||||
|
(!state.activeFlag ||
|
||||||
|
!flags.some((flag) => flag.name === state.activeFlag?.name));
|
||||||
|
|
||||||
|
if (setDefaultFlag) {
|
||||||
|
updates.activeFlag = flags[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const setDefaultProject =
|
||||||
|
projects.length &&
|
||||||
|
(!state.activeProject ||
|
||||||
|
!projects.some(
|
||||||
|
(project) => project.id === state.activeProject,
|
||||||
|
));
|
||||||
|
|
||||||
|
if (setDefaultProject) {
|
||||||
|
updates.activeProject = projects[0].id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(updates).length) {
|
||||||
|
updateState(updates);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
JSON.stringify(projects),
|
||||||
|
JSON.stringify(flags),
|
||||||
|
JSON.stringify(state),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { activeFlag, activeProject } = state;
|
||||||
|
|
||||||
|
const setActiveFlag = (flag: PersonalDashboardSchemaFlagsItem) => {
|
||||||
|
updateState({
|
||||||
|
activeFlag: flag,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const setActiveProject = (projectId: string) => {
|
||||||
|
updateState({
|
||||||
|
activeProject: projectId,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleSectionState = (section: 'flags' | 'projects') => {
|
||||||
|
const property = section === 'flags' ? 'expandFlags' : 'expandProjects';
|
||||||
|
updateState({
|
||||||
|
[property]: !(state[property] ?? true),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
activeFlag,
|
||||||
|
setActiveFlag,
|
||||||
|
activeProject,
|
||||||
|
setActiveProject,
|
||||||
|
expandFlags: state.expandFlags ?? true,
|
||||||
|
expandProjects: state.expandProjects ?? true,
|
||||||
|
toggleSectionState,
|
||||||
|
};
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user