1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-23 00:22:19 +01:00
unleash.unleash/frontend/src/component/personalDashboard/PersonalDashboard.tsx
Thomas Heartman cdeb515488
chore: removes extra border on collapse for the event timeline (#9270)
Fixes a small visual glitch where the event timeline panel (which
usually doesn't have a bottom border on the summary) would get a
bottom border during the collapsing animation.

This happens because to make the border act as we want, we switch
between using the summary's bottom border and the content's top
border, and I'd only updated one of the borders to respect the new
design.
2025-02-10 10:52:32 +01:00

343 lines
12 KiB
TypeScript

import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
import {
Accordion,
AccordionDetails,
AccordionSummary,
Button,
styled,
Typography,
} from '@mui/material';
import { WelcomeDialog } from './WelcomeDialog';
import { useLocalStorageState } from 'hooks/useLocalStorageState';
import { usePersonalDashboard } from 'hooks/api/getters/usePersonalDashboard/usePersonalDashboard';
import { usePersonalDashboardProjectDetails } from 'hooks/api/getters/usePersonalDashboard/usePersonalDashboardProjectDetails';
import { MyProjects } from './MyProjects';
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';
import { usePageTitle } from 'hooks/usePageTitle';
import { fromPersonalDashboardProjectDetailsOutput } from './RemoteData';
import { useEffect } from 'react';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { InfoSection } from './InfoSection';
import { EventTimeline } from 'component/events/EventTimeline/EventTimeline';
import { AccordionContent } from './SharedComponents';
import { Link } from 'react-router-dom';
import { useUiFlag } from 'hooks/useUiFlag';
const WelcomeSection = styled('div')(({ theme }) => ({
display: 'flex',
justifyContent: 'space-between',
gap: theme.spacing(1),
flexFlow: 'row wrap',
alignItems: 'baseline',
}));
const ViewKeyConceptsButton = styled(Button)({
fontWeight: 'normal',
padding: 0,
margin: 0,
});
const SectionAccordion = styled(Accordion, {
shouldForwardProp: (prop) => prop !== 'withSummaryContentBorder',
})<{ withSummaryContentBorder?: boolean }>(
({ theme, withSummaryContentBorder = true }) => {
const borderStyle = `1px solid ${theme.palette.divider}`;
return {
border: `1px solid ${theme.palette.divider}`,
borderRadius: theme.shape.borderRadiusMedium,
backgroundColor: theme.palette.background.paper,
boxShadow: 'none',
'& .expanded': {
'&:before': {
opacity: '0 !important',
},
},
...(withSummaryContentBorder && {
// add a top border to the region when the accordion is collapsed.
// This retains the border between the summary and the region
// during the collapsing animation
"[aria-expanded='false']+.MuiCollapse-root .MuiAccordion-region":
{
borderTop: borderStyle,
},
// add the border to the region for the accordion is expanded
"&>.MuiAccordionSummary-root[aria-expanded='true']": {
borderBottom: borderStyle,
},
}),
overflow: 'hidden',
};
},
);
const StyledAccordionSummary = styled(AccordionSummary)(({ theme }) => ({
border: 'none',
padding: theme.spacing(2, 4),
margin: 0,
// increase specificity to override the default margin
'&>.MuiAccordionSummary-content.MuiAccordionSummary-content': {
margin: '0',
},
}));
const StyledAccordionDetails = styled(AccordionDetails)({
padding: 0,
});
const MainContent = styled('div')(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(2),
}));
const AccordionSummaryText = styled('div')(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(0.5),
}));
const AccordionSummaryHeader = styled('h3')(({ theme }) => ({
color: theme.palette.text.primary,
fontSize: theme.typography.body1.fontSize,
fontWeight: theme.typography.body2.fontWeight,
margin: 0,
}));
const AccordionSummarySubtitle = styled(Typography)(({ theme }) => ({
color: theme.palette.text.secondary,
fontSize: theme.typography.body2.fontSize,
fontWeight: theme.typography.body2.fontWeight,
}));
const EventTimelinePanel = () => {
const { toggleSectionState, expandTimeline } = useDashboardState();
const { trackEvent } = usePlausibleTracker();
const signalsLink = '/integrations/signals';
return (
<SectionAccordion
disableGutters
expanded={expandTimeline ?? false}
onChange={() => toggleSectionState('timeline')}
withSummaryContentBorder={false}
>
<StyledAccordionSummary
expandIcon={
<ExpandMore titleAccess='Toggle timeline section' />
}
id='timeline-panel-header'
aria-controls='timeline-panel-content'
>
<AccordionSummaryText>
<AccordionSummaryHeader>
Event timeline
</AccordionSummaryHeader>
<AccordionSummarySubtitle>
Overview of recent activities across all projects in
Unleash. Make debugging easier and{' '}
<Link
to={signalsLink}
onClick={() => {
trackEvent('event-timeline', {
props: {
eventType: 'signals clicked',
},
});
}}
>
include external signals
</Link>{' '}
to get a fuller overview.
</AccordionSummarySubtitle>
</AccordionSummaryText>
</StyledAccordionSummary>
<StyledAccordionDetails>
<AccordionContent>
<EventTimeline />
</AccordionContent>
</StyledAccordionDetails>
</SectionAccordion>
);
};
const FlagPanel = () => {
const { personalDashboard, refetch: refetchDashboard } =
usePersonalDashboard();
const projects = personalDashboard?.projects || [];
const { activeFlag, setActiveFlag, toggleSectionState, expandFlags } =
useDashboardState({ flags: personalDashboard?.flags ?? [] });
return (
<SectionAccordion
expanded={expandFlags ?? true}
onChange={() => toggleSectionState('flags')}
>
<StyledAccordionSummary
expandIcon={<ExpandMore titleAccess='Toggle flags section' />}
id='flags-panel-header'
aria-controls='flags-panel-content'
>
<AccordionSummaryText>
<AccordionSummaryHeader>
My feature flags
</AccordionSummaryHeader>
<AccordionSummarySubtitle>
Feature flags you have created or favorited
</AccordionSummarySubtitle>
</AccordionSummaryText>
</StyledAccordionSummary>
<StyledAccordionDetails>
<MyFlags
hasProjects={projects?.length > 0}
flagData={
personalDashboard?.flags.length
? {
state: 'flags' as const,
activeFlag,
flags: personalDashboard.flags,
}
: { state: 'no flags' as const }
}
setActiveFlag={setActiveFlag}
refetchDashboard={refetchDashboard}
/>
</StyledAccordionDetails>
</SectionAccordion>
);
};
const ProjectPanel = () => {
const { personalDashboard } = usePersonalDashboard();
const projects = personalDashboard?.projects || [];
const {
activeProject,
setActiveProject,
toggleSectionState,
expandProjects,
} = useDashboardState({ projects });
const personalDashboardProjectDetails =
fromPersonalDashboardProjectDetailsOutput(
usePersonalDashboardProjectDetails(activeProject),
);
return (
<SectionAccordion
disableGutters
expanded={expandProjects ?? true}
onChange={() => toggleSectionState('projects')}
>
<StyledAccordionSummary
expandIcon={
<ExpandMore titleAccess='Toggle projects section' />
}
id='projects-panel-header'
aria-controls='projects-panel-content'
>
<AccordionSummaryText>
<AccordionSummaryHeader>My projects</AccordionSummaryHeader>
<AccordionSummarySubtitle>
Favorite projects, projects you own, and projects you
are a member of
</AccordionSummarySubtitle>
</AccordionSummaryText>
</StyledAccordionSummary>
<StyledAccordionDetails>
<MyProjects
owners={personalDashboard?.projectOwners ?? []}
admins={personalDashboard?.admins ?? []}
projects={projects}
activeProject={activeProject || ''}
setActiveProject={setActiveProject}
personalDashboardProjectDetails={
personalDashboardProjectDetails
}
/>
</StyledAccordionDetails>
</SectionAccordion>
);
};
export const PersonalDashboard = () => {
const { user } = useAuthUser();
const { trackEvent } = usePlausibleTracker();
const { setSplashSeen } = useSplashApi();
const { splash } = useAuthSplash();
const { isOss } = useUiConfig();
const name = user?.name || '';
const showTimelinePanel = useUiFlag('frontendHeaderRedesign');
usePageTitle(name ? `Dashboard: ${name}` : 'Dashboard');
const [welcomeDialog, setWelcomeDialog] = useLocalStorageState<
'open' | 'closed'
>(
'welcome-dialog:v1',
splash?.personalDashboardKeyConcepts ? 'closed' : 'open',
);
useEffect(() => {
trackEvent('personal-dashboard', {
props: {
eventType: 'seen',
},
});
}, []);
return (
<MainContent>
{isOss() ? <InfoSection /> : null}
<WelcomeSection>
<Typography component='h2' variant='h2'>
Welcome {name}
</Typography>
<ViewKeyConceptsButton
sx={{
fontWeight: 'normal',
}}
size={'small'}
variant='text'
onClick={() => {
trackEvent('personal-dashboard', {
props: {
eventType: 'open key concepts',
},
});
setWelcomeDialog('open');
}}
>
View key concepts
</ViewKeyConceptsButton>
</WelcomeSection>
{showTimelinePanel && <EventTimelinePanel />}
<ProjectPanel />
<FlagPanel />
<WelcomeDialog
open={welcomeDialog === 'open'}
onClose={() => {
setSplashSeen('personalDashboardKeyConcepts');
setWelcomeDialog('closed');
}}
/>
</MainContent>
);
};