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:
parent
b7af9b7ec3
commit
01bd877a81
BIN
frontend/src/assets/img/upgradeProjects.png
Normal file
BIN
frontend/src/assets/img/upgradeProjects.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
@ -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 }) => ({
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
@ -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>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user