diff --git a/docs/next-env.d.ts b/docs/next-env.d.ts index 52e831b4342482..a4a7b3f5cfa2f9 100644 --- a/docs/next-env.d.ts +++ b/docs/next-env.d.ts @@ -2,4 +2,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information. +// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information. diff --git a/docs/pages/pricing.tsx b/docs/pages/pricing.tsx index 9ffc17f0704147..2107ba9b4d0e7c 100644 --- a/docs/pages/pricing.tsx +++ b/docs/pages/pricing.tsx @@ -13,7 +13,9 @@ import HeroEnd from 'docs/src/components/home/HeroEnd'; import AppFooter from 'docs/src/layouts/AppFooter'; import BrandingCssVarsProvider from 'docs/src/BrandingCssVarsProvider'; import AppHeaderBanner from 'docs/src/components/banner/AppHeaderBanner'; +import { PrioritySupportProvider } from 'docs/src/components/pricing/PrioritySupportContext'; import { LicenseModelProvider } from 'docs/src/components/pricing/LicenseModelContext'; +import PricingCards from 'docs/src/components/pricing/PricingCards'; export default function Pricing() { return ( @@ -27,16 +29,21 @@ export default function Pricing() {
- - {/* Mobile, Tablet */} - - - - {/* Desktop */} - - - + + + + + + {/* Mobile, Tablet */} + + + + {/* Desktop */} + + + + diff --git a/docs/public/static/branding/pricing/x-plan-enterprise.svg b/docs/public/static/branding/pricing/x-plan-enterprise.svg new file mode 100644 index 00000000000000..0a88d2d3b9ce28 --- /dev/null +++ b/docs/public/static/branding/pricing/x-plan-enterprise.svg @@ -0,0 +1 @@ + diff --git a/docs/src/components/pricing/InfoPrioritySupport.tsx b/docs/src/components/pricing/InfoPrioritySupport.tsx new file mode 100644 index 00000000000000..78c784b24f0fc3 --- /dev/null +++ b/docs/src/components/pricing/InfoPrioritySupport.tsx @@ -0,0 +1,55 @@ +import * as React from 'react'; +import Typography from '@mui/material/Typography'; +import { usePrioritySupport } from 'docs/src/components/pricing/PrioritySupportContext'; + +export default function InfoPrioritySupport(props: { + value: React.ReactNode; + value2?: React.ReactNode; + metadata?: React.ReactNode; + metadata2?: React.ReactNode; +}) { + const { value, value2, metadata, metadata2 } = props; + const { prioritySupport } = usePrioritySupport(); + + return ( + + {prioritySupport ? ( + + + {value} + + + {metadata} + + + ) : ( + + + {value2} + + + {metadata2} + + + )} + + ); +} diff --git a/docs/src/components/pricing/PricingCards.tsx b/docs/src/components/pricing/PricingCards.tsx new file mode 100644 index 00000000000000..147b2def9c81fc --- /dev/null +++ b/docs/src/components/pricing/PricingCards.tsx @@ -0,0 +1,458 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { alpha } from '@mui/material/styles'; +import Button from '@mui/material/Button'; +import Divider from '@mui/material/Divider'; +import Typography from '@mui/material/Typography'; +// import CheckCircleIcon from '@mui/icons-material/CheckCircle'; +import LicenseModelSwitch from 'docs/src/components/pricing/LicenseModelSwitch'; +import { useLicenseModel } from 'docs/src/components/pricing/LicenseModelContext'; +import PrioritySupportSwitch from 'docs/src/components/pricing/PrioritySupportSwitch'; +import { usePrioritySupport } from 'docs/src/components/pricing/PrioritySupportContext'; +import IconImage from 'docs/src/components/icon/IconImage'; +import KeyboardArrowRightRounded from '@mui/icons-material/KeyboardArrowRightRounded'; +import { Link } from '@mui/docs/Link'; +import { PlanName, planInfo } from 'docs/src/components/pricing/PricingTable'; + +const formatter = new Intl.NumberFormat('en-US'); + +function formatCurrency(value: number) { + return `$${formatter.format(value)}`; +} + +interface PlanPriceProps { + plan: 'community' | 'pro' | 'premium' | 'enterprise'; +} + +export function PlanPrice(props: PlanPriceProps) { + const { plan } = props; + + const { licenseModel } = useLicenseModel(); + const annual = licenseModel === 'annual'; + const planPriceMinHeight = 24; + + const { prioritySupport } = usePrioritySupport(); + + if (plan === 'community') { + return ( + + + + $0 + + + + + Free forever! + + + + + ); + } + + const priceUnit = annual ? '/ year / dev' : '/ dev'; + const getPriceExplanation = (displayedValue: number) => { + if (annual) { + return `Equivalent to $${displayedValue} / month / dev`; + } + return ''; + }; + + if (plan === 'pro') { + const annualValue = 180; + const perpetualValue = annualValue * 3; + const monthlyValueForAnnual = annualValue / 12; + + const mainDisplayValue = annual ? annualValue : perpetualValue; + const priceExplanation = getPriceExplanation(annual ? monthlyValueForAnnual : perpetualValue); + + return ( + + + + {formatCurrency(mainDisplayValue)} + + + + {priceUnit} + + + + { + + {priceExplanation} + + } + + + + ); + } + + if (plan === 'premium') { + const premiumAnnualValue = 588; + const premiumPerpetualValue = premiumAnnualValue * 3; + const premiumMonthlyValueForAnnual = premiumAnnualValue / 12; + + const premiumAnnualValueWithPrioritySupport = premiumAnnualValue + 399; + const premiumPerpetualValueWithPrioritySupport = premiumPerpetualValue + 399; + const premiumMonthlyValueForAnnualWithPrioritySupport = 82; // premiumAnnualValueWithPrioritySupport / 12; + + const priceExplanation = getPriceExplanation( + prioritySupport + ? premiumMonthlyValueForAnnualWithPrioritySupport + : premiumMonthlyValueForAnnual, + ); + + let premiumDisplayedValue: number = premiumAnnualValue; + if (annual && prioritySupport) { + premiumDisplayedValue = premiumAnnualValueWithPrioritySupport; + } else if (!annual && prioritySupport) { + premiumDisplayedValue = premiumPerpetualValueWithPrioritySupport; + } else if (annual && !prioritySupport) { + premiumDisplayedValue = premiumAnnualValue; + } else if (!annual && !prioritySupport) { + premiumDisplayedValue = premiumPerpetualValue; + } + + return ( + + + + {formatCurrency(premiumDisplayedValue)} + + + + {priceUnit} + + + + { + + {priceExplanation} + + } + + + + + ); + } + + // else enterprise + return ( + + + {/* */} + + + Custom pricing + + + + Got a bigger team? Request a quote! + + + + + ); +} + +export default function PricingCards() { + return ( + + + + + + + + + + + {planInfo.community.features.map((feature, index) => ( + + + {feature} + + ))} + + + + + + + + + {/* */} + + + {planInfo.pro.features.map((feature, index) => ( + + + {feature} + + ))} + + + + ({ + display: 'flex', + border: '1px solid', + borderColor: 'primary.200', + borderRadius: 1, + flexDirection: 'column', + gap: 3, + py: 3, + px: 2, + flex: '1 1 0px', + background: `${(theme.vars || theme).palette.gradients.linearSubtle}`, + boxShadow: '0px 2px 12px 0px rgba(234, 237, 241, 0.3) inset', + ...theme.applyDarkStyles({ + borderColor: `${alpha(theme.palette.primary[700], 0.4)}`, + boxShadow: '0px 2px 12px 0px rgba(0, 0, 0, 0.25) inset', + }), + })} + > + + + + + {/* */} + + {planInfo.premium.features.map((feature, index) => ( + + {/* */} + + {feature} + + ))} + + + + + + + + + {/* */} + + + {planInfo.enterprise.features.map((feature, index) => ( + + + {feature} + + ))} + + + + + ); +} diff --git a/docs/src/components/pricing/PricingFAQ.tsx b/docs/src/components/pricing/PricingFAQ.tsx index e87cad9037e9e2..dc95fb07856e58 100644 --- a/docs/src/components/pricing/PricingFAQ.tsx +++ b/docs/src/components/pricing/PricingFAQ.tsx @@ -180,6 +180,22 @@ const faqData = [ ), }, + { + summary: 'What is the validity of the priority support add-on?', + detail: ( + + The priority support add-on is valid for 1 year from the date of purchase. It is same for + perpetual or annual license model.{' '} + + Support plans. + + + ), + }, ]; const Accordion = styled(MuiAccordion)(({ theme }) => ({ @@ -265,12 +281,13 @@ export default function PricingFAQ() { {renderItem(1)} {renderItem(2)} {renderItem(3)} + {renderItem(4)} - {renderItem(4)} {renderItem(5)} {renderItem(6)} {renderItem(7)} + {renderItem(8)} ; + plan: 'community' | 'pro' | 'premium' | 'enterprise'; + unavailable?: boolean; } & PaperProps ->(function Plan({ plan, benefits, unavailable, sx, ...props }, ref) { +>(function Plan({ plan, unavailable, sx, ...props }, ref) { const globalTheme = useTheme(); const mode = globalTheme.palette.mode; - const { licenseModel } = useLicenseModel(); + + const { features } = planInfo[plan]; return ( - - + + + {(plan === 'pro' || plan === 'premium') && } - {unavailable ? ( - - ) : ( - - )} - {benefits && - benefits.map((text) => ( - + - - {text} + + {feature} - ))} + + ))} ); }); @@ -126,7 +104,11 @@ export default function PricingList() { label="Pro" sx={{ borderWidth: '0 1px 0 1px', borderStyle: 'solid', borderColor: 'divider' }} /> - + + {planIndex === 0 && ( @@ -152,6 +134,14 @@ export default function PricingList() { )} + {planIndex === 3 && ( + +
+ + +
+
+ )} ); } diff --git a/docs/src/components/pricing/PricingTable.tsx b/docs/src/components/pricing/PricingTable.tsx index c7789a0572181c..863e07dea8cecc 100644 --- a/docs/src/components/pricing/PricingTable.tsx +++ b/docs/src/components/pricing/PricingTable.tsx @@ -14,34 +14,57 @@ import LaunchRounded from '@mui/icons-material/LaunchRounded'; import UnfoldMoreRounded from '@mui/icons-material/UnfoldMoreRounded'; import { Link } from '@mui/docs/Link'; import IconImage from 'docs/src/components/icon/IconImage'; -import LicenseModelSwitch from 'docs/src/components/pricing/LicenseModelSwitch'; import { useLicenseModel } from 'docs/src/components/pricing/LicenseModelContext'; +import SupportAgentIcon from '@mui/icons-material/SupportAgent'; +import { PrioritySupportSwitch2 } from 'docs/src/components/pricing/PrioritySupportSwitch'; +import InfoPrioritySupport from 'docs/src/components/pricing/InfoPrioritySupport'; -const planInfo = { +export const planInfo = { community: { iconName: 'pricing/x-plan-community', title: 'Community', description: 'Get started with the industry-standard React UI library, MIT-licensed.', + features: ['+40 free components', 'Community support'], }, pro: { iconName: 'pricing/x-plan-pro', title: 'Pro', - description: 'Best for professional developers building enterprise or data-rich applications.', + description: 'Best for professional developers or startups building data-rich applications.', + features: [ + 'All Community features and...', + 'MUI X Pro access', + '10+ Pro features', + 'Pro support', + ], }, 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.', + 'The most advanced features for data-rich applications along with standard support.', + features: [ + 'All Pro features and...', + 'MUI X Premium access', + '5+ Premium features', + 'Premium support', + ], + }, + enterprise: { + iconName: 'pricing/x-plan-enterprise', + title: 'Enterprise', + description: + 'All features of Premium coupled with enterprise-grade support and customer success.', + features: [ + 'All Premium features and...', + 'Technical support for all libraries', + 'Guaranteed response time', + 'Pre-screening', + 'Issue escalation', + 'Customer success manager', + ], }, } as const; -const formatter = new Intl.NumberFormat('en-US'); - -function formatCurrency(value: number) { - return `$${formatter.format(value)}`; -} - // TODO: Collapse should expose an API to customize the duration based on the height. function transitionTheme(theme: any) { return { @@ -62,9 +85,9 @@ function transitionTheme(theme: any) { export function PlanName({ plan, - disableDescription = false, + disableDescription = true, }: { - plan: 'community' | 'pro' | 'premium'; + plan: 'community' | 'pro' | 'premium' | 'enterprise'; disableDescription?: boolean; }) { const { title, iconName, description } = planInfo[plan]; @@ -102,134 +125,9 @@ export function PlanName({ ); } -interface PlanPriceProps { - plan: 'community' | 'pro' | 'premium'; -} - -export function PlanPrice(props: PlanPriceProps) { - const { plan } = props; - - const { licenseModel } = useLicenseModel(); - const annual = licenseModel === 'annual'; - const planPriceMinHeight = 24; - - if (plan === 'community') { - return ( - - - - $0 - - - - Free forever! - - - ); - } - - const monthlyDisplay = annual; - - const priceUnit = monthlyDisplay ? '/ month / dev' : '/ dev'; - const getPriceExplanation = (displayedValue: number) => { - if (!annual) { - return `$${displayedValue}/dev billed once.`; - } - return monthlyDisplay - ? `Billed annually at $${displayedValue}/dev.` - : `$${displayedValue}/dev/month billed annualy.`; - }; - - if (plan === 'pro') { - const monthlyValue = annual ? 15 : 15 * 3; - const annualValue = monthlyValue * 12; - - const mainDisplayValue = monthlyDisplay ? monthlyValue : annualValue; - const priceExplanation = getPriceExplanation(monthlyDisplay ? annualValue : monthlyValue); - - return ( - - - - - {formatCurrency(mainDisplayValue)} - - - - {priceUnit} - - - - {(annual || monthlyDisplay) && ( - - {priceExplanation} - - )} - - - ); - } - // else Premium - const premiumMonthlyValue = annual ? 49 : 49 * 3; - const premiumAnnualValue = premiumMonthlyValue * 12; - - const premiumDisplayedValue = monthlyDisplay ? premiumMonthlyValue : premiumAnnualValue; - const priceExplanation = getPriceExplanation( - monthlyDisplay ? premiumAnnualValue : premiumMonthlyValue, - ); - - return ( - - - - - - {formatCurrency(premiumDisplayedValue)} - - - - {priceUnit} - - - - {(annual || monthlyDisplay) && ( - - {priceExplanation} - - )} - - - ); -} - function Info(props: { value: React.ReactNode; metadata?: React.ReactNode }) { const { value, metadata } = props; + return ( {typeof value === 'string' ? ( @@ -403,7 +301,7 @@ function RowHead({ sx={[ { justifyContent: 'flex-start', - borderRadius: 1, + borderRadius: '12px 0 0 12px', p: 1, transition: 'none', typography: 'body2', @@ -653,7 +551,12 @@ const rowHeaders: Record = { 'core-support': ( + Technical support for + MUI Core + + ), tooltip: 'Support for MUI Core (for example Material UI) is provided by the community. MUI Core maintainers focus on solving root issues to support the community at large.', }} @@ -662,12 +565,25 @@ const rowHeaders: Record = { 'x-support': ( + Technical support for + MUI X + + ), tooltip: 'You can ask for technical support, report bugs and submit unlimited feature requests to the advanced components. We take your subscription plan as one of the prioritization criteria.', }} /> ), + 'priority-support': ( + + ), 'tech-advisory': ( = { ), 'response-time': ( ), 'pre-screening': ( @@ -716,6 +635,14 @@ const rowHeaders: Record = { }} /> ), + 'customer-success': ( + + ), }; const yes = ; @@ -830,12 +757,14 @@ const communityData: Record = { // Support 'core-support': , 'x-support': , + 'priority-support': no, 'tech-advisory': no, 'support-duration': no, 'response-time': no, 'pre-screening': no, 'issue-escalation': no, 'security-questionnaire': no, + 'customer-success': no, }; const proData: Record = { @@ -934,12 +863,14 @@ const proData: Record = { // Support 'core-support': , 'x-support': , + 'priority-support': no, 'tech-advisory': no, 'support-duration': , 'response-time': no, 'pre-screening': no, 'issue-escalation': no, 'security-questionnaire': , + 'customer-success': no, }; const premiumData: Record = { @@ -1035,26 +966,121 @@ const premiumData: Record = { 'mui-x-development-perpetual': , 'mui-x-updates': , // Support - 'core-support': , + 'core-support': , 'x-support': , + 'priority-support': , 'tech-advisory': pending, 'support-duration': , - 'response-time': ( - - Available later on -
- 2 business days. -
1 business day (priority add-on only) - - } - /> - ), - 'pre-screening': , - 'issue-escalation': , + 'response-time': , + 'pre-screening': , + 'issue-escalation': , 'security-questionnaire': , + 'customer-success': no, +}; + +const enterpriseData: Record = { + // Core + 'Base UI': yes, + 'MUI System': yes, + 'Material UI': yes, + 'Joy UI': yes, + // MUI X + // data grid - columns + 'data-grid/column-groups': yes, + 'data-grid/column-spanning': yes, + 'data-grid/column-resizing': yes, + 'data-grid/column-autosizing': yes, + 'data-grid/column-reorder': yes, + 'data-grid/column-pinning': yes, + // data grid - rows + 'data-grid/row-height': yes, + 'data-grid/row-spanning': yes, + 'data-grid/row-reordering': yes, + 'data-grid/row-pinning': yes, + 'data-grid/row-selection': yes, + 'data-grid/row-multiselection': yes, + 'data-grid/row-cell-selection': yes, + // data grid - filter + 'data-grid/filter-quick': yes, + 'data-grid/filter-column': yes, + 'data-grid/header-filters': yes, + 'data-grid/filter-multicolumn': yes, + 'data-grid/column-sorting': yes, + 'data-grid/multi-column-sorting': yes, + 'data-grid/pagination': yes, + 'data-grid/pagination-large': yes, + // data grid - edit + 'data-grid/edit-row': yes, + 'data-grid/edit-cell': yes, + // data grid - export + 'data-grid/file-csv': yes, + 'data-grid/file-print': yes, + 'data-grid/file-clipboard-copy': yes, + 'data-grid/file-clipboard-paste': yes, + 'data-grid/file-excel': yes, + 'data-grid/customizable-components': yes, + 'data-grid/virtualize-column': yes, + 'data-grid/virtualize-row': yes, + 'data-grid/tree-data': yes, + 'data-grid/master-detail': yes, + 'data-grid/grouping': yes, + 'data-grid/aggregation': yes, + 'data-grid/pivoting': pending, + 'data-grid/accessibility': yes, + 'data-grid/keyboard-nav': yes, + 'data-grid/localization': yes, + 'date-picker/simple': yes, + 'date-picker/range': yes, + + // charts - components + 'charts/line': yes, + 'charts/bar': yes, + 'charts/scatter': yes, + 'charts/pie': yes, + 'charts/sparkline': yes, + 'charts/gauge': yes, + 'charts/heatmap': yes, + 'charts/treemap': pending, + 'charts/radar': pending, + 'charts/funnel': pending, + 'charts/sankey': pending, + 'charts/gantt': pending, + 'charts/gantt-advanced': toBeDefined, + 'charts/candlestick': toBeDefined, + 'charts/large-dataset': toBeDefined, + // charts - features + 'charts/legend': yes, + 'charts/tooltip': yes, + 'charts/zoom-and-pan': yes, + 'charts/export': pending, + // charts - datagrid + 'charts/cell-with-charts': yes, + 'charts/filter-interaction': pending, + 'charts/selection-interaction': pending, + // Tree View + 'tree-view/simple-tree-view': yes, + 'tree-view/rich-tree-view': yes, + 'tree-view/selection': yes, + 'tree-view/multi-selection': yes, + 'tree-view/inline-editing': yes, + 'tree-view/drag-to-reorder': yes, + 'tree-view/virtualization': pending, + // general + 'mui-x-production': yes, + 'mui-x-development': , + 'mui-x-development-perpetual': , + 'mui-x-updates': , + // Support + 'core-support': yes, + 'x-support': , + 'priority-support': , + 'tech-advisory': pending, + 'support-duration': , + 'response-time': , + 'pre-screening': , + 'issue-escalation': , + 'security-questionnaire': , + 'customer-success': yes, }; function RowCategory(props: BoxProps) { @@ -1148,13 +1174,13 @@ function StickyHead({ Plans - {(['community', 'pro', 'premium'] as const).map((plan) => ( + {(['community', 'pro', 'premium', 'enterprise'] as const).map((plan) => ( @@ -1189,6 +1215,7 @@ function renderMasterRow(key: string, gridSx: object, plans: Array) { {id === 'community' && communityData[key]} {id === 'pro' && proData[key]} {id === 'premium' && premiumData[key]} + {id === 'enterprise' && enterpriseData[key]} ))}
@@ -1204,49 +1231,6 @@ function PricingTableDevelopment(props: any) { : renderRow('mui-x-development-perpetual'); } -function PricingTableBuyPro() { - const { licenseModel } = useLicenseModel(); - - return ( - - ); -} - -function PricingTableBuyPremium() { - const { licenseModel } = useLicenseModel(); - - return ( - - ); -} - const StyledCollapse = styled(Collapse, { name: 'MuiSlider', slot: 'Track', @@ -1265,11 +1249,11 @@ const StyledCollapse = styled(Collapse, { export default function PricingTable({ columnHeaderHidden, - plans = ['community', 'pro', 'premium'], + plans = ['community', 'pro', 'premium', 'enterprise'], ...props }: BoxProps & { columnHeaderHidden?: boolean; - plans?: Array<'community' | 'pro' | 'premium'>; + plans?: Array<'community' | 'pro' | 'premium' | 'enterprise'>; }) { const router = useRouter(); const [dataGridCollapsed, setDataGridCollapsed] = React.useState(false); @@ -1326,7 +1310,7 @@ export default function PricingTable({ return ( - + {!columnHeaderHidden && ( @@ -1335,31 +1319,16 @@ export default function PricingTable({ - - -
- - -
- +
- - + + +
)} }> @@ -1392,6 +1361,9 @@ export default function PricingTable({ {dataGridUnfoldMore} + + {dataGridUnfoldMore} +