mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +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