diff --git a/frontend/src/component/personalDashboard/FlagMetricsChart.tsx b/frontend/src/component/personalDashboard/FlagMetricsChart.tsx index 31f7ab284887..8312459f74ed 100644 --- a/frontend/src/component/personalDashboard/FlagMetricsChart.tsx +++ b/frontend/src/component/personalDashboard/FlagMetricsChart.tsx @@ -121,12 +121,10 @@ const useFlagMetrics = ( environment: string | null, hoursBack: number, ) => { - const { - featureMetrics: metrics = [], - loading, - error, - } = useFeatureMetricsRaw(flagName, hoursBack); - + const { featureMetrics: metrics = [], loading } = useFeatureMetricsRaw( + flagName, + hoursBack, + ); const sortedMetrics = useMemo(() => { return [...metrics].sort((metricA, metricB) => { return metricA.timestamp.localeCompare(metricB.timestamp); @@ -153,7 +151,7 @@ const useFlagMetrics = ( return createBarChartOptions(theme, hoursBack, locationSettings); }, [theme, hoursBack, locationSettings]); - return { data, options, loading, error }; + return { data, options, loading }; }; const EnvironmentSelect: FC<{ @@ -224,22 +222,11 @@ export const FlagMetricsChart: FC<{ const { environment, setEnvironment, activeEnvironments } = useMetricsEnvironments(flag.project, flag.name); - const { - data, - options, - loading, - error: metricsError, - } = useFlagMetrics(flag.name, environment, hoursBack); - - if (metricsError) { - return ( - - - - ); - } + const { data, options, loading } = useFlagMetrics( + flag.name, + environment, + hoursBack, + ); const noData = data.datasets[0].data.length === 0; diff --git a/frontend/src/component/personalDashboard/Grid.tsx b/frontend/src/component/personalDashboard/Grid.tsx index 97e6a8d05834..11f25ecf3d6f 100644 --- a/frontend/src/component/personalDashboard/Grid.tsx +++ b/frontend/src/component/personalDashboard/Grid.tsx @@ -58,7 +58,7 @@ export const FlagGrid = styled(ContentGrid)( ); export const GridItem = styled('div', { - shouldForwardProp: (prop) => !['gridArea'].includes(prop.toString()), + shouldForwardProp: (prop) => !['gridArea', 'sx'].includes(prop.toString()), })<{ gridArea: string }>(({ theme, gridArea }) => ({ padding: theme.spacing(2, 4), maxHeight: '100%', @@ -113,20 +113,3 @@ 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', - }), -); diff --git a/frontend/src/component/personalDashboard/MyFlags.tsx b/frontend/src/component/personalDashboard/MyFlags.tsx deleted file mode 100644 index fb75cda0da2c..000000000000 --- a/frontend/src/component/personalDashboard/MyFlags.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import { type FC, useEffect, useRef } from 'react'; -import { - ContentGridContainer, - FlagGrid, - ListItemBox, - SpacedGridItem, - StyledCardTitle, - StyledList, - listItemStyle, -} from './Grid'; -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(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 ( - - - - - {flag.name} - { - trackEvent('personal-dashboard', { - props: { - eventType: `Go to flag from list`, - }, - }); - }} - size='small' - sx={{ ml: 'auto' }} - > - - - - - - ); -}; - -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 = ({ - hasProjects, - flagData, - setActiveFlag, - refetchDashboard, -}) => { - return ( - - - - {flagData.state === 'flags' ? ( - - {flagData.flags.map((flag) => ( - setActiveFlag(flag)} - /> - ))} - - ) : hasProjects ? ( - - - You have not created or favorited any feature - flags. Once you do, they will show up here. - - - To create a new flag, go to one of your - projects. - - - ) : ( - - 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. - - )} - - - - {flagData.state === 'flags' ? ( - - ) : ( - - )} - - - - ); -}; - -const FlagMetricsChart = React.lazy(() => - import('./FlagMetricsChart').then((module) => ({ - default: module.FlagMetricsChart, - })), -); -const PlaceholderFlagMetricsChart = React.lazy(() => - import('./FlagMetricsChart').then((module) => ({ - default: module.PlaceholderFlagMetricsChartWithWrapper, - })), -); diff --git a/frontend/src/component/personalDashboard/MyProjects.tsx b/frontend/src/component/personalDashboard/MyProjects.tsx index f58b76f8ebb1..70062e4f7f12 100644 --- a/frontend/src/component/personalDashboard/MyProjects.tsx +++ b/frontend/src/component/personalDashboard/MyProjects.tsx @@ -13,6 +13,7 @@ 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, @@ -27,7 +28,6 @@ import { GridItem, SpacedGridItem, StyledList, - StyledCardTitle, } from './Grid'; import { ContactAdmins, DataError } from './ProjectDetailsError'; import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; diff --git a/frontend/src/component/personalDashboard/PersonalDashboard.tsx b/frontend/src/component/personalDashboard/PersonalDashboard.tsx index dd7be3bc8513..be9a8d954a76 100644 --- a/frontend/src/component/personalDashboard/PersonalDashboard.tsx +++ b/frontend/src/component/personalDashboard/PersonalDashboard.tsx @@ -3,23 +3,201 @@ 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'; -import { useDashboardState } from './useDashboardState'; -import { MyFlags } from './MyFlags'; + +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(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 ( + + + + + {flag.name} + { + trackEvent('personal-dashboard', { + props: { + eventType: `Go to flag from list`, + }, + }); + }} + size='small' + sx={{ ml: 'auto' }} + > + + + + + + ); +}; + +// 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( + 'personal-dashboard:v1', + defaultState, + ); + + const updateState = (newState: Partial) => + setState({ ...defaultState, ...state, ...newState }); + + useEffect(() => { + const updates: Partial = {}; + 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 }) => ({ display: 'flex', @@ -86,28 +264,6 @@ const NoActiveFlagsInfo = styled('div')(({ theme }) => ({ gap: theme.spacing(2), })); -type DashboardState = - | { - state: 'flags and projects'; - // regular state; show everything - activeFlag: any; - activeProject: any; - } - | { - state: 'projects, no flags'; - // show projects as normal, tell the user to create a flag - activeProject: any; - } - | { - state: 'no projects, no flags'; - // no projects and no flags; show information about admins, project owners, and tell the user to join a project to create a flags - } - | { - state: 'flags, no projects'; - // show info about admins + project owners, regular flags - activeFlag: any; - }; - export const PersonalDashboard = () => { const { user } = useAuthUser(); const { trackEvent } = usePlausibleTracker(); @@ -116,11 +272,8 @@ export const PersonalDashboard = () => { const name = user?.name; - const { - personalDashboard, - refetch: refetchDashboard, - loading: personalDashboardLoading, - } = usePersonalDashboard(); + const { personalDashboard, refetch: refetchDashboard } = + usePersonalDashboard(); const projects = personalDashboard?.projects || []; @@ -232,22 +385,70 @@ export const PersonalDashboard = () => { - 0} - flagData={ - personalDashboard && - personalDashboard.flags.length && - activeFlag - ? { - state: 'flags' as const, - activeFlag, - flags: personalDashboard.flags, - } - : { state: 'no flags' as const } - } - setActiveFlag={setActiveFlag} - refetchDashboard={refetchDashboard} - /> + + + + {personalDashboard && + personalDashboard.flags.length > 0 ? ( + + {personalDashboard.flags.map((flag) => ( + + setActiveFlag(flag) + } + /> + ))} + + ) : activeProject ? ( + + + You have not created or favorited + any feature flags. Once you do, they + will show up here. + + + To create a new flag, go to one of + your projects. + + + ) : ( + + 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. + + )} + + + + {activeFlag ? ( + + ) : ( + + )} + + + { ); }; + +const FlagMetricsChart = React.lazy(() => + import('./FlagMetricsChart').then((module) => ({ + default: module.FlagMetricsChart, + })), +); +const PlaceholderFlagMetricsChart = React.lazy(() => + import('./FlagMetricsChart').then((module) => ({ + default: module.PlaceholderFlagMetricsChartWithWrapper, + })), +); diff --git a/frontend/src/component/personalDashboard/useDashboardState.ts b/frontend/src/component/personalDashboard/useDashboardState.ts deleted file mode 100644 index e1275c089c42..000000000000 --- a/frontend/src/component/personalDashboard/useDashboardState.ts +++ /dev/null @@ -1,95 +0,0 @@ -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( - 'personal-dashboard:v1', - defaultState, - ); - - const updateState = (newState: Partial) => - setState({ ...defaultState, ...state, ...newState }); - - useEffect(() => { - const updates: Partial = {}; - 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, - }; -};