mirror of
https://github.com/Unleash/unleash.git
synced 2025-03-18 00:19:49 +01:00
Refactor premium feature (#2627)
https://linear.app/unleash/issue/2-491/improve-premiumfeature-component-and-how-its-implemented  Refactors `PremiumFeature` to be a bit more straightforward and match new designs, both in standalone and tooltip modes. This also fixes the fact that the change requests tab did not display the premium feature info, along with other smaller fixes. Co-authored-by: Christopher Kolstad <chriswk@getunleash.ai>
This commit is contained in:
parent
fb2c30244c
commit
1be2483e6a
@ -271,7 +271,7 @@ export const ChangeRequestsTabs = ({
|
|||||||
}
|
}
|
||||||
elseShow={
|
elseShow={
|
||||||
<TablePlaceholder>
|
<TablePlaceholder>
|
||||||
None of the changes where submitted yet.
|
None of the changes were submitted yet.
|
||||||
</TablePlaceholder>
|
</TablePlaceholder>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -5,12 +5,17 @@ import { useProjectNameOrId } from 'hooks/api/getters/useProject/useProject';
|
|||||||
import { ChangeRequestsTabs } from './ChangeRequestsTabs/ChangeRequestsTabs';
|
import { ChangeRequestsTabs } from './ChangeRequestsTabs/ChangeRequestsTabs';
|
||||||
import { SortingRule } from 'react-table';
|
import { SortingRule } from 'react-table';
|
||||||
import { useProjectChangeRequests } from 'hooks/api/getters/useProjectChangeRequests/useProjectChangeRequests';
|
import { useProjectChangeRequests } from 'hooks/api/getters/useProjectChangeRequests/useProjectChangeRequests';
|
||||||
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||||
|
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||||
|
import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature';
|
||||||
|
|
||||||
const defaultSort: SortingRule<string> = { id: 'updatedAt', desc: true };
|
const defaultSort: SortingRule<string> = { id: 'updatedAt', desc: true };
|
||||||
|
|
||||||
export const ProjectChangeRequests = () => {
|
export const ProjectChangeRequests = () => {
|
||||||
const projectId = useRequiredPathParam('projectId');
|
const projectId = useRequiredPathParam('projectId');
|
||||||
const projectName = useProjectNameOrId(projectId);
|
const projectName = useProjectNameOrId(projectId);
|
||||||
|
const { isOss, isPro } = useUiConfig();
|
||||||
|
|
||||||
usePageTitle(`Change requests – ${projectName}`);
|
usePageTitle(`Change requests – ${projectName}`);
|
||||||
|
|
||||||
@ -21,6 +26,14 @@ export const ProjectChangeRequests = () => {
|
|||||||
defaultSort
|
defaultSort
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isOss() || isPro()) {
|
||||||
|
return (
|
||||||
|
<PageContent sx={{ justifyContent: 'center' }}>
|
||||||
|
<PremiumFeature feature="Change Requests" />
|
||||||
|
</PageContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChangeRequestsTabs
|
<ChangeRequestsTabs
|
||||||
changeRequests={changeRequests}
|
changeRequests={changeRequests}
|
||||||
|
@ -1,86 +1,152 @@
|
|||||||
import { ReactComponent as ProPlanIcon } from 'assets/icons/pro-enterprise-feature-badge.svg';
|
import { ReactComponent as ProPlanIcon } from 'assets/icons/pro-enterprise-feature-badge.svg';
|
||||||
import { Box, Link, styled, Typography } from '@mui/material';
|
import { Box, Button, Link, styled, Typography } from '@mui/material';
|
||||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||||
|
import { ConditionallyRender } from '../ConditionallyRender/ConditionallyRender';
|
||||||
|
|
||||||
const PremiumFeatureWrapper = styled(Box)(({ theme }) => ({
|
const PremiumFeatureWrapper = styled(Box, {
|
||||||
|
shouldForwardProp: prop => prop !== 'tooltip',
|
||||||
|
})<{ tooltip?: boolean }>(({ theme, tooltip }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
padding: theme.spacing(1, 0.5),
|
alignItems: tooltip ? 'start' : 'center',
|
||||||
|
textAlign: tooltip ? 'left' : 'center',
|
||||||
|
backgroundColor: tooltip ? 'transparent' : theme.palette.secondaryContainer,
|
||||||
|
borderRadius: tooltip ? 0 : theme.shape.borderRadiusLarge,
|
||||||
|
padding: tooltip ? theme.spacing(1, 0.5) : theme.spacing(7.5, 1),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledTitle = styled(Typography)(({ theme }) => ({
|
const StyledTitle = styled(Typography)(({ theme }) => ({
|
||||||
display: 'inline-flex',
|
display: 'inline-flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
fontWeight: theme.fontWeight.bold,
|
fontWeight: theme.fontWeight.bold,
|
||||||
fontSize: theme.fontSizes.smallBody,
|
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledBody = styled(Typography)(({ theme }) => ({
|
const StyledBody = styled('div', {
|
||||||
|
shouldForwardProp: prop => prop !== 'tooltip',
|
||||||
|
})<{ tooltip?: boolean }>(({ theme, tooltip }) => ({
|
||||||
|
margin: tooltip ? theme.spacing(1, 0) : theme.spacing(3, 0, 5, 0),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledTypography = styled(Typography)(({ theme }) => ({
|
||||||
fontSize: theme.fontSizes.smallBody,
|
fontSize: theme.fontSizes.smallBody,
|
||||||
margin: theme.spacing(1, 0),
|
}));
|
||||||
|
|
||||||
|
const StyledButtonContainer = styled('div')(() => ({
|
||||||
|
display: 'flex',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledLink = styled(Link)(({ theme }) => ({
|
const StyledLink = styled(Link)(({ theme }) => ({
|
||||||
fontSize: theme.fontSizes.smallBody,
|
fontSize: theme.fontSizes.smallBody,
|
||||||
width: 'fit-content',
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
enum FeatureLevelTitle {
|
enum FeaturePlan {
|
||||||
PRO = 'Pro & Enterprise feature',
|
PRO = 'Pro & Enterprise',
|
||||||
ENTERPRISE = 'Enterprise feature',
|
ENTERPRISE = 'Enterprise',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum PlausibleOrigin {
|
const PremiumFeatures = {
|
||||||
PROJECT = 'Projects',
|
['Adding new projects']: {
|
||||||
ACCESS = 'Access',
|
plan: FeaturePlan.PRO,
|
||||||
CHANGE_REQUEST = 'Change Request',
|
url: '',
|
||||||
}
|
},
|
||||||
|
['Access']: {
|
||||||
|
plan: FeaturePlan.PRO,
|
||||||
|
url: 'https://docs.getunleash.io/reference/rbac',
|
||||||
|
},
|
||||||
|
['Change Requests']: {
|
||||||
|
plan: FeaturePlan.ENTERPRISE,
|
||||||
|
url: 'https://docs.getunleash.io/reference/change-requests',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
type PremiumFeature = keyof typeof PremiumFeatures;
|
||||||
|
|
||||||
|
const UPGRADE_URL = 'https://www.getunleash.io/plans';
|
||||||
|
|
||||||
export interface PremiumFeatureProps {
|
export interface PremiumFeatureProps {
|
||||||
children: React.ReactNode;
|
feature: PremiumFeature;
|
||||||
origin?: PlausibleOrigin;
|
tooltip?: boolean;
|
||||||
center?: boolean;
|
|
||||||
enterpriseOnly?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PremiumFeature = ({
|
export const PremiumFeature = ({ feature, tooltip }: PremiumFeatureProps) => {
|
||||||
children,
|
const { url, plan } = PremiumFeatures[feature];
|
||||||
origin,
|
|
||||||
center,
|
|
||||||
enterpriseOnly = false,
|
|
||||||
}: PremiumFeatureProps) => {
|
|
||||||
const tracker = usePlausibleTracker();
|
const tracker = usePlausibleTracker();
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
if (origin) {
|
tracker.trackEvent('upgrade_plan_clicked', {
|
||||||
tracker.trackEvent('upgrade_plan_clicked', {
|
props: { feature },
|
||||||
props: { origin },
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const featureLabel = Boolean(url) ? (
|
||||||
|
<StyledLink href={url} target="_blank">
|
||||||
|
{feature}
|
||||||
|
</StyledLink>
|
||||||
|
) : (
|
||||||
|
feature
|
||||||
|
);
|
||||||
|
|
||||||
|
const featureMessage = (
|
||||||
|
<>
|
||||||
|
{featureLabel} is a feature available for the{' '}
|
||||||
|
<strong>{plan}</strong>{' '}
|
||||||
|
{plan === FeaturePlan.PRO ? 'plans' : 'plan'}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PremiumFeatureWrapper
|
<PremiumFeatureWrapper tooltip={tooltip}>
|
||||||
sx={{
|
|
||||||
alignItems: center ? 'center' : 'start',
|
|
||||||
textAlign: center ? 'center' : 'left',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<StyledTitle>
|
<StyledTitle>
|
||||||
<ProPlanIcon />
|
<ProPlanIcon />
|
||||||
{enterpriseOnly
|
{`${plan} feature`}
|
||||||
? FeatureLevelTitle.ENTERPRISE
|
|
||||||
: FeatureLevelTitle.PRO}
|
|
||||||
</StyledTitle>
|
</StyledTitle>
|
||||||
<StyledBody>{children}</StyledBody>
|
<ConditionallyRender
|
||||||
<StyledLink
|
condition={Boolean(tooltip)}
|
||||||
href={'https://www.getunleash.io/plans'}
|
show={
|
||||||
target="_blank"
|
<>
|
||||||
onClick={handleClick}
|
<StyledBody tooltip>
|
||||||
>
|
<StyledTypography>
|
||||||
Upgrade now
|
{featureMessage}. You need to upgrade your plan
|
||||||
</StyledLink>
|
if you want to use it
|
||||||
|
</StyledTypography>
|
||||||
|
</StyledBody>
|
||||||
|
<StyledButtonContainer>
|
||||||
|
<StyledLink
|
||||||
|
href={UPGRADE_URL}
|
||||||
|
target="_blank"
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
Upgrade now
|
||||||
|
</StyledLink>
|
||||||
|
</StyledButtonContainer>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
elseShow={
|
||||||
|
<>
|
||||||
|
<StyledBody>
|
||||||
|
<StyledTypography>
|
||||||
|
{featureMessage}
|
||||||
|
</StyledTypography>
|
||||||
|
<StyledTypography>
|
||||||
|
You need to upgrade your plan if you want to use
|
||||||
|
it
|
||||||
|
</StyledTypography>
|
||||||
|
</StyledBody>
|
||||||
|
<StyledButtonContainer>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
href={UPGRADE_URL}
|
||||||
|
target="_blank"
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
Upgrade now
|
||||||
|
</Button>
|
||||||
|
</StyledButtonContainer>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</PremiumFeatureWrapper>
|
</PremiumFeatureWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import { Alert, Link, styled } from '@mui/material';
|
import { Alert } from '@mui/material';
|
||||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||||
import AccessContext from 'contexts/AccessContext';
|
import AccessContext from 'contexts/AccessContext';
|
||||||
import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions';
|
import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions';
|
||||||
@ -9,15 +9,8 @@ import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
|||||||
import { usePageTitle } from 'hooks/usePageTitle';
|
import { usePageTitle } from 'hooks/usePageTitle';
|
||||||
import { useProjectNameOrId } from 'hooks/api/getters/useProject/useProject';
|
import { useProjectNameOrId } from 'hooks/api/getters/useProject/useProject';
|
||||||
import { ChangeRequestTable } from './ChangeRequestTable';
|
import { ChangeRequestTable } from './ChangeRequestTable';
|
||||||
import {
|
import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature';
|
||||||
PlausibleOrigin,
|
import { ChangeRequestProcessHelp } from './ChangeRequestProcessHelp/ChangeRequestProcessHelp';
|
||||||
PremiumFeature,
|
|
||||||
} from 'component/common/PremiumFeature/PremiumFeature';
|
|
||||||
|
|
||||||
const StyledLink = styled(Link)(({ theme }) => ({
|
|
||||||
fontSize: theme.fontSizes.smallBody,
|
|
||||||
width: 'fit-content',
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const ChangeRequestConfiguration = () => {
|
export const ChangeRequestConfiguration = () => {
|
||||||
const projectId = useRequiredPathParam('projectId');
|
const projectId = useRequiredPathParam('projectId');
|
||||||
@ -25,37 +18,29 @@ export const ChangeRequestConfiguration = () => {
|
|||||||
const { hasAccess } = useContext(AccessContext);
|
const { hasAccess } = useContext(AccessContext);
|
||||||
const { isOss, isPro } = useUiConfig();
|
const { isOss, isPro } = useUiConfig();
|
||||||
|
|
||||||
usePageTitle(`Project change request – ${projectName}`);
|
usePageTitle(`Project change request configuration – ${projectName}`);
|
||||||
|
|
||||||
if (isOss() || isPro()) {
|
if (isOss() || isPro()) {
|
||||||
return (
|
return (
|
||||||
<PageContent
|
<PageContent
|
||||||
header={<PageHeader title="Change request configuration" />}
|
header={
|
||||||
|
<PageHeader
|
||||||
|
titleElement="Change request configuration"
|
||||||
|
actions={<ChangeRequestProcessHelp />}
|
||||||
|
/>
|
||||||
|
}
|
||||||
sx={{ justifyContent: 'center' }}
|
sx={{ justifyContent: 'center' }}
|
||||||
>
|
>
|
||||||
<PremiumFeature
|
<PremiumFeature feature="Change Requests" />
|
||||||
origin={PlausibleOrigin.CHANGE_REQUEST}
|
|
||||||
enterpriseOnly
|
|
||||||
center
|
|
||||||
>
|
|
||||||
<>
|
|
||||||
If you want to use{' '}
|
|
||||||
<StyledLink
|
|
||||||
href={'https://www.getunleash.io/plans'} // TODO: Add link to change request docs when available
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
"Change Requests"
|
|
||||||
</StyledLink>{' '}
|
|
||||||
you will need to upgrade to Enterprise plan
|
|
||||||
</>
|
|
||||||
</PremiumFeature>
|
|
||||||
</PageContent>
|
</PageContent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasAccess(UPDATE_PROJECT, projectId)) {
|
if (!hasAccess(UPDATE_PROJECT, projectId)) {
|
||||||
return (
|
return (
|
||||||
<PageContent header={<PageHeader title="Project access" />}>
|
<PageContent
|
||||||
|
header={<PageHeader title="Change request configuration" />}
|
||||||
|
>
|
||||||
<Alert severity="error">
|
<Alert severity="error">
|
||||||
You need project owner permissions to access this section.
|
You need project owner permissions to access this section.
|
||||||
</Alert>
|
</Alert>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import { Alert, Box, Link, styled } from '@mui/material';
|
import { Alert } from '@mui/material';
|
||||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||||
import AccessContext from 'contexts/AccessContext';
|
import AccessContext from 'contexts/AccessContext';
|
||||||
import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions';
|
import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions';
|
||||||
@ -9,15 +9,7 @@ import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
|||||||
import { usePageTitle } from 'hooks/usePageTitle';
|
import { usePageTitle } from 'hooks/usePageTitle';
|
||||||
import { ProjectAccessTable } from 'component/project/ProjectAccess/ProjectAccessTable/ProjectAccessTable';
|
import { ProjectAccessTable } from 'component/project/ProjectAccess/ProjectAccessTable/ProjectAccessTable';
|
||||||
import { useProjectNameOrId } from 'hooks/api/getters/useProject/useProject';
|
import { useProjectNameOrId } from 'hooks/api/getters/useProject/useProject';
|
||||||
import {
|
import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature';
|
||||||
PlausibleOrigin,
|
|
||||||
PremiumFeature,
|
|
||||||
} from 'component/common/PremiumFeature/PremiumFeature';
|
|
||||||
|
|
||||||
const StyledLink = styled(Link)(({ theme }) => ({
|
|
||||||
fontSize: theme.fontSizes.smallBody,
|
|
||||||
width: 'fit-content',
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const ProjectAccess = () => {
|
export const ProjectAccess = () => {
|
||||||
const projectId = useRequiredPathParam('projectId');
|
const projectId = useRequiredPathParam('projectId');
|
||||||
@ -28,37 +20,18 @@ export const ProjectAccess = () => {
|
|||||||
|
|
||||||
if (isOss()) {
|
if (isOss()) {
|
||||||
return (
|
return (
|
||||||
<PageContent header={<PageHeader title="Project access" />}>
|
<PageContent
|
||||||
<Box
|
header={<PageHeader title="Access" />}
|
||||||
sx={{
|
sx={{ justifyContent: 'center' }}
|
||||||
display: 'inline-flex',
|
>
|
||||||
maxWidth: '50%',
|
<PremiumFeature feature="Access" />
|
||||||
margin: '0 25%',
|
|
||||||
}}
|
|
||||||
alignSelf={'center'}
|
|
||||||
>
|
|
||||||
<PremiumFeature origin={PlausibleOrigin.ACCESS} center>
|
|
||||||
<>
|
|
||||||
Controlling access to projects requires a paid
|
|
||||||
version of Unleash. Check out{' '}
|
|
||||||
<StyledLink
|
|
||||||
href="https://www.getunleash.io"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
getunleash.io
|
|
||||||
</StyledLink>{' '}
|
|
||||||
to find out more.
|
|
||||||
</>
|
|
||||||
</PremiumFeature>
|
|
||||||
</Box>
|
|
||||||
</PageContent>
|
</PageContent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasAccess(UPDATE_PROJECT, projectId)) {
|
if (!hasAccess(UPDATE_PROJECT, projectId)) {
|
||||||
return (
|
return (
|
||||||
<PageContent header={<PageHeader title="Project access" />}>
|
<PageContent header={<PageHeader title="Access" />}>
|
||||||
<Alert severity="error">
|
<Alert severity="error">
|
||||||
You need project owner permissions to access this section.
|
You need project owner permissions to access this section.
|
||||||
</Alert>
|
</Alert>
|
||||||
|
@ -20,10 +20,7 @@ import { TablePlaceholder } from 'component/common/Table';
|
|||||||
import { useMediaQuery } from '@mui/material';
|
import { useMediaQuery } from '@mui/material';
|
||||||
import theme from 'themes/theme';
|
import theme from 'themes/theme';
|
||||||
import { Search } from 'component/common/Search/Search';
|
import { Search } from 'component/common/Search/Search';
|
||||||
import {
|
import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature';
|
||||||
PlausibleOrigin,
|
|
||||||
PremiumFeature,
|
|
||||||
} from 'component/common/PremiumFeature/PremiumFeature';
|
|
||||||
import { ITooltipResolverProps } from 'component/common/TooltipResolver/TooltipResolver';
|
import { ITooltipResolverProps } from 'component/common/TooltipResolver/TooltipResolver';
|
||||||
import { ReactComponent as ProPlanIcon } from 'assets/icons/pro-enterprise-feature-badge.svg';
|
import { ReactComponent as ProPlanIcon } from 'assets/icons/pro-enterprise-feature-badge.svg';
|
||||||
|
|
||||||
@ -48,10 +45,7 @@ function resolveCreateButtonData(
|
|||||||
disabled: true,
|
disabled: true,
|
||||||
tooltip: {
|
tooltip: {
|
||||||
titleComponent: (
|
titleComponent: (
|
||||||
<PremiumFeature origin={PlausibleOrigin.PROJECT}>
|
<PremiumFeature feature="Adding new projects" tooltip />
|
||||||
To be able to add more projects you need to upgrade to
|
|
||||||
Pro or Enterprise plan
|
|
||||||
</PremiumFeature>
|
|
||||||
),
|
),
|
||||||
sx: { maxWidth: '320px' },
|
sx: { maxWidth: '320px' },
|
||||||
variant: 'custom',
|
variant: 'custom',
|
||||||
|
Loading…
Reference in New Issue
Block a user