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, | ||||
|     ProjectGrid, | ||||
|     GridItem, | ||||
| } from './Grid'; | ||||
| } from './SharedComponents'; | ||||
| 
 | ||||
| const PaddedEmptyGridItem = styled(EmptyGridItem)(({ theme }) => ({ | ||||
|     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 { RoleAndOwnerInfo } from './RoleAndOwnerInfo'; | ||||
| import { forwardRef, useEffect, useRef, type FC } from 'react'; | ||||
| import { StyledCardTitle } from './PersonalDashboard'; | ||||
| import type { | ||||
|     PersonalDashboardProjectDetailsSchema, | ||||
|     PersonalDashboardSchemaAdminsItem, | ||||
| @ -28,7 +27,8 @@ import { | ||||
|     GridItem, | ||||
|     SpacedGridItem, | ||||
|     StyledList, | ||||
| } from './Grid'; | ||||
|     StyledCardTitle, | ||||
| } from './SharedComponents'; | ||||
| import { ContactAdmins, DataError } from './ProjectDetailsError'; | ||||
| import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; | ||||
| 
 | ||||
|  | ||||
| @ -3,201 +3,23 @@ import { | ||||
|     Accordion, | ||||
|     AccordionDetails, | ||||
|     AccordionSummary, | ||||
|     Alert, | ||||
|     Button, | ||||
|     IconButton, | ||||
|     Link, | ||||
|     ListItem, | ||||
|     ListItemButton, | ||||
|     styled, | ||||
|     Typography, | ||||
| } from '@mui/material'; | ||||
| import React, { type FC, useEffect, useRef } from 'react'; | ||||
| import LinkIcon from '@mui/icons-material/ArrowForward'; | ||||
| import { WelcomeDialog } from './WelcomeDialog'; | ||||
| import { useLocalStorageState } from 'hooks/useLocalStorageState'; | ||||
| 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 useLoading from '../../hooks/useLoading'; | ||||
| import { MyProjects } from './MyProjects'; | ||||
| import { | ||||
|     ContentGridContainer, | ||||
|     FlagGrid, | ||||
|     ListItemBox, | ||||
|     listItemStyle, | ||||
|     SpacedGridItem, | ||||
|     StyledList, | ||||
| } from './Grid'; | ||||
| import { ContentGridNoProjects } from './ContentGridNoProjects'; | ||||
| import ExpandMore from '@mui/icons-material/ExpandMore'; | ||||
| import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; | ||||
| import useSplashApi from 'hooks/api/actions/useSplashApi/useSplashApi'; | ||||
| import { useAuthSplash } from 'hooks/api/getters/useAuth/useAuthSplash'; | ||||
| 
 | ||||
| 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', | ||||
|     }), | ||||
| ); | ||||
| 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, | ||||
|     }; | ||||
| }; | ||||
| import { useDashboardState } from './useDashboardState'; | ||||
| import { MyFlags } from './MyFlags'; | ||||
| 
 | ||||
| const WelcomeSection = styled('div')(({ theme }) => ({ | ||||
|     display: 'flex', | ||||
| @ -258,12 +80,6 @@ const MainContent = styled('div')(({ theme }) => ({ | ||||
|     gap: theme.spacing(2), | ||||
| })); | ||||
| 
 | ||||
| const NoActiveFlagsInfo = styled('div')(({ theme }) => ({ | ||||
|     display: 'flex', | ||||
|     flexFlow: 'column', | ||||
|     gap: theme.spacing(2), | ||||
| })); | ||||
| 
 | ||||
| export const PersonalDashboard = () => { | ||||
|     const { user } = useAuthUser(); | ||||
|     const { trackEvent } = usePlausibleTracker(); | ||||
| @ -385,70 +201,20 @@ export const PersonalDashboard = () => { | ||||
|                     </Typography> | ||||
|                 </StyledAccordionSummary> | ||||
|                 <StyledAccordionDetails> | ||||
|                     <ContentGridContainer> | ||||
|                         <FlagGrid> | ||||
|                             <SpacedGridItem gridArea='flags'> | ||||
|                                 {personalDashboard && | ||||
|                                 personalDashboard.flags.length > 0 ? ( | ||||
|                                     <StyledList | ||||
|                                         disablePadding={true} | ||||
|                                         sx={{ | ||||
|                                             height: '100%', | ||||
|                                             overflow: 'auto', | ||||
|                                         }} | ||||
|                                     > | ||||
|                                         {personalDashboard.flags.map((flag) => ( | ||||
|                                             <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> | ||||
|                     <MyFlags | ||||
|                         hasProjects={projects?.length > 0} | ||||
|                         flagData={ | ||||
|                             personalDashboard && personalDashboard.flags.length | ||||
|                                 ? { | ||||
|                                       state: 'flags' as const, | ||||
|                                       activeFlag, | ||||
|                                       flags: personalDashboard.flags, | ||||
|                                   } | ||||
|                                 : { state: 'no flags' as const } | ||||
|                         } | ||||
|                         setActiveFlag={setActiveFlag} | ||||
|                         refetchDashboard={refetchDashboard} | ||||
|                     /> | ||||
|                 </StyledAccordionDetails> | ||||
|             </SectionAccordion> | ||||
|             <WelcomeDialog | ||||
| @ -461,14 +227,3 @@ export const PersonalDashboard = () => { | ||||
|         </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', { | ||||
|     shouldForwardProp: (prop) => !['gridArea', 'sx'].includes(prop.toString()), | ||||
|     shouldForwardProp: (prop) => !['gridArea'].includes(prop.toString()), | ||||
| })<{ gridArea: string }>(({ theme, gridArea }) => ({ | ||||
|     padding: theme.spacing(2, 4), | ||||
|     maxHeight: '100%', | ||||
| @ -113,3 +113,20 @@ export const StyledList = styled(List)(({ theme }) => ({ | ||||
|         maxHeight: '100%', | ||||
|     })({ 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