1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01: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',
justifyContent: 'space-between',
height: '100%',
position: 'relative',
}));
export const StyledDivHeader = styled('div')(({ theme }) => ({

View File

@ -11,7 +11,7 @@ interface IProjectCardFooterProps {
isFavorite?: boolean;
children?: React.ReactNode;
disabled?: boolean;
owners: IProjectOwnersProps['owners'];
owners?: IProjectOwnersProps['owners'];
}
const StyledFooter = styled(Box)<{ disabled: boolean }>(
@ -34,7 +34,7 @@ export const ProjectCardFooter: FC<IProjectCardFooterProps> = ({
}) => {
return (
<StyledFooter disabled={disabled}>
<ProjectOwners owners={owners} />
{owners ? <ProjectOwners owners={owners} /> : null}
{children}
</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 { Link } from 'react-router-dom';
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 loadingData from './loadingData';
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 { flexColumn } from 'themes/themeStyles';
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,
}));
import { UpgradeProjectCard } from '../ProjectCard/UpgradeProjectCard';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
const StyledGridContainer = styled('div')(({ theme }) => ({
display: 'grid',
@ -56,41 +38,18 @@ type ProjectGroupProps = {
};
export const ProjectGroup = ({
sectionTitle,
sectionSubtitle,
HeaderActions,
projects,
loading,
placeholder = 'No projects available.',
ProjectCardComponent,
link = true,
}: ProjectGroupProps) => {
const ProjectCard = ProjectCardComponent ?? NewProjectCard;
const ProjectCard = ProjectCardComponent ?? DefaultProjectCard;
const { isOss } = useUiConfig();
const { searchQuery } = useSearchHighlightContext();
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
condition={projects.length < 1 && !loading}
show={
@ -157,9 +116,10 @@ export const ProjectGroup = ({
</>
)}
/>
{isOss() ? <UpgradeProjectCard /> : null}
</StyledGridContainer>
}
/>
</StyledContainer>
</>
);
};

View File

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