Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[website] Proposal for new Pro plan #41962

Draft
wants to merge 3 commits into
base: next
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
199 changes: 176 additions & 23 deletions docs/src/components/pricing/PricingTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ import { Link } from '@mui/docs/Link';
import IconImage from 'docs/src/components/icon/IconImage';
import LicensingModelSwitch from 'docs/src/components/pricing/LicensingModelSwitch';
import { useLicensingModel } from 'docs/src/components/pricing/LicensingModelContext';
import { SvgIcon } from '@mui/material';
import Checkbox from '@mui/material/Checkbox';
import FormControlLabel from '@mui/material/FormControlLabel';
import { useTheme } from '@mui/styles';

const planInfo = {
community: {
Expand All @@ -26,13 +30,14 @@ const planInfo = {
pro: {
iconName: 'pricing/x-plan-pro',
title: 'Pro',
description: 'Best for professional developers building enterprise or data-rich applications.',
description:
'Best for professionals, with the flexibility to select the essential components for your application.',
},
premium: {
iconName: 'pricing/x-plan-premium',
title: 'Premium',
description:
'The most advanced features for data-rich applications, as well as the highest priority for support.',
'A complete enterprise bundle with the most advanced features and a higher priority support.',
},
} as const;

Expand Down Expand Up @@ -69,7 +74,7 @@ export function PlanName({
justifyContent: 'center',
alignItems: 'baseline',
mt: 1,
minHeight: { md: 63 },
minHeight: 70,
}}
>
{description}
Expand All @@ -79,16 +84,138 @@ export function PlanName({
);
}

enum Components {
DataGrid = 'Data Grid',
DatePickers = 'Date and Time pickers',
Charts = 'Charts',
TreeView = 'Tree View',
}

interface ComponentSelectorProps {
includedComponents: Components[];
setIncludedComponents?: (components: Components[]) => void;
premium?: boolean;
}

export function ComponentSelector(props: ComponentSelectorProps) {
const theme = useTheme();
const { includedComponents, setIncludedComponents, premium } = props;

const getCustomIcon = (checked: boolean, disabled: boolean) => (
<SvgIcon sx={{ fontSize: 24 }}>
<rect
x="3"
y="3"
width="18"
height="18"
rx="2"
strokeWidth="1"
//fill="none"
//stroke={disabled ? theme.palette.action.disabled : 'grey'}
fill={
checked && !disabled
? theme.palette.primary.main
: disabled
? theme.palette.action.disabled
: 'none'
}
stroke={
checked && !disabled
? theme.palette.primary.main
: disabled
? theme.palette.action.disabled
: 'grey'
}
/>
{checked && (
<path
strokeWidth="3"
d="M7 12l3 3 7-7"
fill="none"
//stroke={disabled ? theme.palette.action.disabled : theme.palette.primary.main}
stroke={'#fff'}
/>
)}
</SvgIcon>
);
const handleCheckboxChange = (component: Components, isChecked: boolean) => {
if (!setIncludedComponents) {
return;
}
if (isChecked) {
// Add the component if it's checked and not already included
setIncludedComponents((prev) => [...prev, component]);
} else {
// Remove the component if it's unchecked
if (includedComponents.length > 1) {
setIncludedComponents((prev) => prev.filter((c) => c !== component));
}
}
};

return (
<div
style={{
display: 'flex',
flexDirection: 'column',
width: 'fit-content',
margin: 'auto',
marginBottom: '20px',
}}
>
{Object.values(Components).map((component) => {
const isLastOne = includedComponents.length === 1 && includedComponents.includes(component);
return (
<Tooltip
key={'component selector tooltip'}
title={isLastOne ? 'At least one component must be selected.' : ''}
placement="top"
disableHoverListener={!isLastOne} // Disable tooltip when not the last one
>
<FormControlLabel
key={component}
sx={{
height: '30px',
margin: 0,
color: theme.palette.secondary.main, // Use secondary color for label
}}
control={
<Checkbox
checked={includedComponents.includes(component)}
onChange={(e) => handleCheckboxChange(component, e.target.checked)}
disabled={premium}
icon={getCustomIcon(false, premium || false)}
checkedIcon={getCustomIcon(true, premium || false)}
/>
}
label={
<Typography
variant="body2"
color={premium ? 'text.disabled' : 'text.secondary'}
textAlign="center"
>
{component}
</Typography>
}
/>
</Tooltip>
);
})}
</div>
);
}

interface PlanPriceProps {
plan: 'community' | 'pro' | 'premium';
includedComponents?: Components[];
}

export function PlanPrice(props: PlanPriceProps) {
const { plan } = props;
const { plan, includedComponents } = props;

const { licensingModel } = useLicensingModel();
const annual = licensingModel === 'annual';
const planPriceMinHeight = 64;
const planPriceMinHeight = 44;

if (plan === 'community') {
return (
Expand All @@ -99,7 +226,7 @@ export function PlanPrice(props: PlanPriceProps) {
component="div"
fontWeight="bold"
color="success.600"
sx={{ mt: 4.5 }}
sx={{ mt: 5.5 }}
>
$0
</Typography>
Expand All @@ -124,7 +251,10 @@ export function PlanPrice(props: PlanPriceProps) {
};

if (plan === 'pro') {
const monthlyValue = annual ? 15 : 15 * 3;
const numberOfComponentsCovered = includedComponents?.length || 1;

const monthlyValue =
numberOfComponentsCovered * (annual ? 9 : 8 * 3) - 2 * (numberOfComponentsCovered - 1);
const annualValue = monthlyValue * 12;

const mainDisplayValue = monthlyDisplay ? monthlyValue : annualValue;
Expand All @@ -133,7 +263,7 @@ export function PlanPrice(props: PlanPriceProps) {
return (
<React.Fragment>
<LicensingModelSwitch />
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', mt: 1, mb: 4 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', mt: 1, mb: 2 }}>
<Typography variant="h3" component="div" fontWeight="bold" color="primary.main">
{formatCurrency(mainDisplayValue)}
</Typography>
Expand All @@ -144,24 +274,26 @@ export function PlanPrice(props: PlanPriceProps) {
</Box>
<Box sx={{ minHeight: planPriceMinHeight }}>
{(annual || monthlyDisplay) && (
<Typography variant="body2" color="text.secondary" textAlign="center">
<Typography
variant="body2"
color="text.secondary"
textAlign="center"
sx={{ fontWeight: '500' }}
>
{priceExplanation}
</Typography>
)}
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }} textAlign="center">
{/*<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }} textAlign="center">
{'No additional fee beyond 10 devs.'}
</Typography>
</Typography>*/}
</Box>
</React.Fragment>
);
}
// else Premium

const originalPriceMultiplicator = monthlyDisplay ? 1 : 12;
const premiumOriginalValue = annual
? 49 * originalPriceMultiplicator
: 49 * 3 * originalPriceMultiplicator;
const premiumMonthlyValue = annual ? 37 : 37 * 3;
const premiumOriginalValue = 49;
const premiumMonthlyValue = annual ? premiumOriginalValue : premiumOriginalValue * 3;
const premiumAnnualValue = premiumMonthlyValue * 12;

const premiumDisplayedValue = monthlyDisplay ? premiumMonthlyValue : premiumAnnualValue;
Expand All @@ -172,8 +304,8 @@ export function PlanPrice(props: PlanPriceProps) {
return (
<React.Fragment>
<LicensingModelSwitch />
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', mt: 1, mb: 4 }}>
<Typography
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', mt: 1, mb: 2 }}>
{/*<Typography
variant="caption"
fontWeight="medium"
sx={(theme) => ({
Expand All @@ -196,7 +328,7 @@ export function PlanPrice(props: PlanPriceProps) {
})}
>
{formatCurrency(premiumOriginalValue)}
</Typography>
</Typography>*/}
<Box sx={{ width: 10 }} />
<Typography variant="h3" component="div" fontWeight="bold" color="primary.main">
{formatCurrency(premiumDisplayedValue)}
Expand All @@ -208,13 +340,18 @@ export function PlanPrice(props: PlanPriceProps) {
</Box>
<Box sx={{ minHeight: planPriceMinHeight }}>
{(annual || monthlyDisplay) && (
<Typography variant="body2" color="text.secondary" textAlign="center">
<Typography
variant="body2"
color="text.secondary"
textAlign="center"
sx={{ fontWeight: '500' }}
>
{priceExplanation}
</Typography>
)}
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }} textAlign="center">
{/*<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }} textAlign="center">
🐦 Early bird special (25% off).
</Typography>
</Typography>*/}
</Box>
</React.Fragment>
);
Expand Down Expand Up @@ -1238,6 +1375,9 @@ export default function PricingTable({
const router = useRouter();
const [dataGridCollapsed, setDataGridCollapsed] = React.useState(false);
const [chartsCollapsed, setChartsCollapsed] = React.useState(false);
const [includedComponentsOnPro, setIncludedComponentsOnPro] = React.useState<Components[]>([
Components.DataGrid,
]);

React.useEffect(() => {
if (router.query['expand-path'] === 'all') {
Expand Down Expand Up @@ -1306,13 +1446,26 @@ export default function PricingTable({
<ColumnHeadHighlight>
<div>
<PlanName plan="pro" />
<PlanPrice plan="pro" />
<PlanPrice plan="pro" includedComponents={includedComponentsOnPro} />
<ComponentSelector
includedComponents={includedComponentsOnPro}
setIncludedComponents={setIncludedComponentsOnPro}
/>
</div>
<PricingTableBuyPro />
</ColumnHeadHighlight>
<Box sx={{ display: 'flex', flexDirection: 'column', p: 2, pt: 1.5 }}>
<PlanName plan="premium" />
<PlanPrice plan="premium" />
<ComponentSelector
includedComponents={[
Components.DataGrid,
Components.Charts,
Components.DatePickers,
Components.TreeView,
]}
premium={true}
/>
<PricingTableBuyPremium />
</Box>
</Box>
Expand Down