1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-26 13:48:33 +02:00

feat: info about unlimited projects option (#8814)

- refactored projects list header
- added info about unlimited projects to open-source version
This commit is contained in:
Tymoteusz Czech 2024-11-20 16:40:19 +01:00 committed by GitHub
parent b7af9b7ec3
commit 01bd877a81
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 209 additions and 75 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -32,6 +32,7 @@ export const StyledProjectCardBody = styled(Box)(({ theme }) => ({
flexFlow: 'column', flexFlow: 'column',
justifyContent: 'space-between', justifyContent: 'space-between',
height: '100%', height: '100%',
position: 'relative',
})); }));
export const StyledDivHeader = styled('div')(({ theme }) => ({ export const StyledDivHeader = styled('div')(({ theme }) => ({

View File

@ -11,7 +11,7 @@ interface IProjectCardFooterProps {
isFavorite?: boolean; isFavorite?: boolean;
children?: React.ReactNode; children?: React.ReactNode;
disabled?: boolean; disabled?: boolean;
owners: IProjectOwnersProps['owners']; owners?: IProjectOwnersProps['owners'];
} }
const StyledFooter = styled(Box)<{ disabled: boolean }>( const StyledFooter = styled(Box)<{ disabled: boolean }>(
@ -34,7 +34,7 @@ export const ProjectCardFooter: FC<IProjectCardFooterProps> = ({
}) => { }) => {
return ( return (
<StyledFooter disabled={disabled}> <StyledFooter disabled={disabled}>
<ProjectOwners owners={owners} /> {owners ? <ProjectOwners owners={owners} /> : null}
{children} {children}
</StyledFooter> </StyledFooter>
); );

View File

@ -0,0 +1,110 @@
import { IconButton, styled, Tooltip, Typography } from '@mui/material';
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
import CloseIcon from '@mui/icons-material/Close';
import { StyledProjectCard, StyledProjectCardBody } from './ProjectCard.styles';
import { ProjectCardFooter } from './ProjectCardFooter/ProjectCardFooter';
import upgradeProjects from 'assets/img/upgradeProjects.png';
import { formatAssetPath } from 'utils/formatPath';
import { useLocalStorageState } from 'hooks/useLocalStorageState';
const StyledFooter = styled('div')(({ theme }) => ({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: theme.spacing(0.5, 1, 0.5, 2),
height: 53,
}));
const StyledCloseButton = styled(IconButton)(({ theme }) => ({
position: 'absolute',
top: theme.spacing(0.75),
right: theme.spacing(0.75),
}));
const StyledInfo = styled('a')(({ theme }) => ({
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: theme.spacing(1),
textDecoration: 'none',
color: 'inherit',
height: '100%',
paddingTop: theme.spacing(0.5),
}));
const StyledImage = styled('img')(({ theme }) => ({
width: 95,
margin: theme.spacing(1),
}));
export const UpgradeProjectCard = () => {
const [moreProjectsUpgrade, setMoreProjectsUpgrade] = useLocalStorageState<
'open' | 'closed'
>('upgrade-projects:v1', 'open');
if (moreProjectsUpgrade === 'closed') {
return null;
}
const onDismiss = () => {
setMoreProjectsUpgrade('closed');
};
return (
<StyledProjectCard>
<StyledProjectCardBody>
<Tooltip title='Dismiss' arrow>
<StyledCloseButton
aria-label='dismiss'
onClick={onDismiss}
size='small'
>
<CloseIcon fontSize='inherit' />
</StyledCloseButton>
</Tooltip>
<StyledInfo
href='https://www.getunleash.io/upgrade-unleash?utm_source=projects'
target='_blank'
>
<Typography component='span' fontWeight='bold'>
More{' '}
<Typography
component='span'
color='secondary'
fontWeight='bold'
>
projects
</Typography>{' '}
<br />
easy collaboration
</Typography>
<StyledImage
src={formatAssetPath(upgradeProjects)}
alt='Upgrade projects'
/>
</StyledInfo>
</StyledProjectCardBody>
<ProjectCardFooter>
<StyledFooter>
<Typography
variant='body2'
color='text.secondary'
lineHeight={1.2}
>
Get unlimited projects, and scale Unleash in your
organization
</Typography>
<IconButton
color='primary'
href='https://www.getunleash.io/upgrade-unleash?utm_source=projects'
target='_blank'
>
<ArrowForwardIcon />
</IconButton>
</StyledFooter>
</ProjectCardFooter>
</StyledProjectCard>
);
};

View File

@ -1,32 +1,14 @@
import type { ComponentType, ReactNode } from 'react'; import type { ComponentType, ReactNode } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { ProjectCard as NewProjectCard } from '../ProjectCard/ProjectCard'; import { ProjectCard as DefaultProjectCard } from '../ProjectCard/ProjectCard';
import type { ProjectSchema } from 'openapi'; import type { ProjectSchema } from 'openapi';
import loadingData from './loadingData'; import loadingData from './loadingData';
import { TablePlaceholder } from 'component/common/Table'; import { TablePlaceholder } from 'component/common/Table';
import { styled, Typography } from '@mui/material'; import { styled } from '@mui/material';
import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
import { flexColumn } from 'themes/themeStyles'; import { UpgradeProjectCard } from '../ProjectCard/UpgradeProjectCard';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
const StyledContainer = styled('article')(({ theme }) => ({
...flexColumn,
gap: theme.spacing(2),
}));
const StyledHeaderContainer = styled('div')(({ theme }) => ({
display: 'flex',
flexDirection: 'column-reverse',
gap: theme.spacing(2),
[theme.breakpoints.up('md')]: {
flexDirection: 'row',
alignItems: 'flex-end',
},
}));
const StyledHeaderTitle = styled('div')(() => ({
flexGrow: 0,
}));
const StyledGridContainer = styled('div')(({ theme }) => ({ const StyledGridContainer = styled('div')(({ theme }) => ({
display: 'grid', display: 'grid',
@ -56,41 +38,18 @@ type ProjectGroupProps = {
}; };
export const ProjectGroup = ({ export const ProjectGroup = ({
sectionTitle,
sectionSubtitle,
HeaderActions,
projects, projects,
loading, loading,
placeholder = 'No projects available.', placeholder = 'No projects available.',
ProjectCardComponent, ProjectCardComponent,
link = true, link = true,
}: ProjectGroupProps) => { }: ProjectGroupProps) => {
const ProjectCard = ProjectCardComponent ?? NewProjectCard; const ProjectCard = ProjectCardComponent ?? DefaultProjectCard;
const { isOss } = useUiConfig();
const { searchQuery } = useSearchHighlightContext(); const { searchQuery } = useSearchHighlightContext();
return ( return (
<StyledContainer> <>
<StyledHeaderContainer>
<StyledHeaderTitle>
<ConditionallyRender
condition={Boolean(sectionTitle)}
show={
<Typography component='h2' variant='h2'>
{sectionTitle}
</Typography>
}
/>
<ConditionallyRender
condition={Boolean(sectionSubtitle)}
show={
<Typography variant='body2' color='text.secondary'>
{sectionSubtitle}
</Typography>
}
/>
</StyledHeaderTitle>
{HeaderActions}
</StyledHeaderContainer>
<ConditionallyRender <ConditionallyRender
condition={projects.length < 1 && !loading} condition={projects.length < 1 && !loading}
show={ show={
@ -157,9 +116,10 @@ export const ProjectGroup = ({
</> </>
)} )}
/> />
{isOss() ? <UpgradeProjectCard /> : null}
</StyledGridContainer> </StyledGridContainer>
} }
/> />
</StyledContainer> </>
); );
}; };

View File

@ -16,6 +16,8 @@ import { ProjectCreationButton } from './ProjectCreationButton/ProjectCreationBu
import { useGroupedProjects } from './hooks/useGroupedProjects'; import { useGroupedProjects } from './hooks/useGroupedProjects';
import { useProjectsSearchAndSort } from './hooks/useProjectsSearchAndSort'; import { useProjectsSearchAndSort } from './hooks/useProjectsSearchAndSort';
import { ProjectArchiveLink } from './ProjectArchiveLink/ProjectArchiveLink'; import { ProjectArchiveLink } from './ProjectArchiveLink/ProjectArchiveLink';
import { ProjectsListHeader } from './ProjectsListHeader/ProjectsListHeader';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
const StyledApiError = styled(ApiError)(({ theme }) => ({ const StyledApiError = styled(ApiError)(({ theme }) => ({
maxWidth: '500px', maxWidth: '500px',
@ -30,6 +32,7 @@ const StyledContainer = styled('div')(({ theme }) => ({
export const ProjectList = () => { export const ProjectList = () => {
const { projects, loading, error, refetch } = useProjects(); const { projects, loading, error, refetch } = useProjects();
const { isOss } = useUiConfig();
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md')); const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
@ -63,7 +66,7 @@ export const ProjectList = () => {
actions={ actions={
<> <>
<ConditionallyRender <ConditionallyRender
condition={!isSmallScreen} condition={!isOss && !isSmallScreen}
show={ show={
<> <>
<Search <Search
@ -88,7 +91,7 @@ export const ProjectList = () => {
} }
> >
<ConditionallyRender <ConditionallyRender
condition={isSmallScreen} condition={!isOss() && isSmallScreen}
show={ show={
<Search <Search
initialValue={state.query || ''} initialValue={state.query || ''}
@ -110,29 +113,42 @@ export const ProjectList = () => {
)} )}
/> />
<SearchHighlightProvider value={state.query || ''}> <SearchHighlightProvider value={state.query || ''}>
<ProjectGroup <div>
sectionTitle='My projects' <ProjectsListHeader
sectionSubtitle='Favorite projects, projects you own, and projects you are a member of' subtitle='Favorite projects, projects you own, and projects you are a member of'
HeaderActions={ actions={
<ProjectsListSort <ProjectsListSort
sortBy={state.sortBy} sortBy={state.sortBy}
setSortBy={(sortBy) => setSortBy={(sortBy) =>
setState({ setState({
sortBy: sortBy as typeof state.sortBy, sortBy: sortBy as typeof state.sortBy,
}) })
} }
/>
}
>
My projects
</ProjectsListHeader>
<ProjectGroup
loading={loading}
projects={
isOss()
? sortedProjects
: groupedProjects.myProjects
}
/>
</div>
{!isOss() ? (
<div>
<ProjectsListHeader subtitle='Projects in Unleash that you have access to.'>
Other projects
</ProjectsListHeader>
<ProjectGroup
loading={loading}
projects={groupedProjects.otherProjects}
/> />
} </div>
loading={loading} ) : null}
projects={groupedProjects.myProjects}
/>
<ProjectGroup
sectionTitle='Other projects'
sectionSubtitle='Projects in Unleash that you have access to.'
loading={loading}
projects={groupedProjects.otherProjects}
/>
</SearchHighlightProvider> </SearchHighlightProvider>
</StyledContainer> </StyledContainer>
</PageContent> </PageContent>

View File

@ -0,0 +1,47 @@
import { styled, Typography } from '@mui/material';
import type { FC, ReactNode } from 'react';
type ProjectsListHeaderProps = {
children?: ReactNode;
subtitle?: string;
actions?: ReactNode;
};
const StyledHeaderContainer = styled('div')(({ theme }) => ({
display: 'flex',
flexDirection: 'column-reverse',
gap: theme.spacing(2),
[theme.breakpoints.up('md')]: {
flexDirection: 'row',
alignItems: 'flex-end',
},
marginBottom: theme.spacing(2),
}));
const StyledHeaderTitle = styled('div')(() => ({
flexGrow: 0,
}));
export const ProjectsListHeader: FC<ProjectsListHeaderProps> = ({
children,
subtitle,
actions,
}) => {
return (
<StyledHeaderContainer>
<StyledHeaderTitle>
{children ? (
<Typography component='h2' variant='h2'>
{children}
</Typography>
) : null}
{subtitle ? (
<Typography variant='body2' color='text.secondary'>
{subtitle}
</Typography>
) : null}
</StyledHeaderTitle>
{actions}
</StyledHeaderContainer>
);
};