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,
- };
-};