diff --git a/src/components/Pricing/PricingAddons/index.js b/src/components/Pricing/PricingAddons/index.js
index a54f031bb0f2c..0e6176d262306 100644
--- a/src/components/Pricing/PricingAddons/index.js
+++ b/src/components/Pricing/PricingAddons/index.js
@@ -39,16 +39,18 @@ import {
getToggleButtonStyle,
getSliderStyle
} from "./styles";
-import { formatAndConvertPrice, formatSliderPrice } from "../../../utils/currencies";
-export const PricingAddons = ({ isYearly = false, setIsYearly, currency, enterprisePlan }) => {
+import { formatAndConvertPrice } from "../../../utils/currencies";
+
+export const PricingAddons = ({ isYearly = false, setIsYearly ,currency,enterprisePlan }) => {
const [selectedAddon, setSelectedAddon] = useState(null);
- // const [quantity, setQuantity] = useState(1);
+ const [addonMenuOpen, setAddonMenuOpen] = useState(false);
const quantity = 1;
const [selectedSubAddOns, setSelectedSubAddOns] = useState({});
const [totalPrice, setTotalPrice] = useState(0);
const [quantityIndex, setQuantityIndex] = useState(0);
const [enterpriseUsers, setEnterpriseUsers] = useState(1);
+ const [perAddonState, setPerAddonState] = useState({});
const { isDark } = useStyledDarkMode();
const theme = useTheme();
@@ -57,14 +59,49 @@ export const PricingAddons = ({ isYearly = false, setIsYearly, currency, enterpr
return getAddOns();
}, []);
+ useEffect(() => {
+ if (!addonMenuOpen) return;
+ const y = window.scrollY || window.pageYOffset || 0;
+
+
+ const html = document.documentElement;
+ const body = document.body;
+
+ const prevHtmlOverflow = html.style.overflow;
+ const prevHtmlTouchAction = html.style.touchAction;
+ const prevBodyOverflow = body.style.overflow;
+ const prevBodyPosition = body.style.position;
+ const prevBodyTop = body.style.top;
+ const prevBodyWidth = body.style.width;
+
+ html.style.overflow = "hidden";
+ html.style.touchAction = "none";
+ body.style.overflow = "hidden";
+ body.style.position = "fixed";
+ body.style.top = `-${y}px`;
+ body.style.width = "100%";
+
+ return () => {
+ html.style.overflow = prevHtmlOverflow;
+ html.style.touchAction = prevHtmlTouchAction;
+ body.style.overflow = prevBodyOverflow;
+ body.style.position = prevBodyPosition;
+ body.style.top = prevBodyTop;
+ body.style.width = prevBodyWidth;
+ window.scrollTo(0, y);
+ };
+ }, [addonMenuOpen]);
+
// Helper function to render icons based on type
const renderIcon = (iconType) => {
switch (iconType) {
- case "academy":
- return ;
+ case "academy":
+ return (
+
+ );
case "cloud":
return ;
case "group":
@@ -74,15 +111,13 @@ export const PricingAddons = ({ isYearly = false, setIsYearly, currency, enterpr
}
};
- const formatPrice = (price) => {
- return formatAndConvertPrice(price, currency);
- };
+ const formatPrice = (price) => formatAndConvertPrice(price, currency);
useEffect(() => {
if (selectedAddon) {
let baseTotal = 0;
if (selectedAddon.id === "academy") {
- const theorySubAddon = selectedAddon.subAddOns?.find(sub => sub.id === "academy-theory");
+ const theorySubAddon = selectedAddon.subAddOns?.find((sub) => sub.id === "academy-theory");
if (theorySubAddon?.pricing && theorySubAddon.pricing[quantityIndex]) {
const currentLearnerOption = theorySubAddon.pricing[quantityIndex];
const monthlyPerUserCost = currentLearnerOption.monthlyPerUser;
@@ -91,20 +126,18 @@ export const PricingAddons = ({ isYearly = false, setIsYearly, currency, enterpr
? yearlyPerUserCost * currentLearnerOption.learners
: monthlyPerUserCost * currentLearnerOption.learners;
}
- } else {
- if (selectedAddon.pricing && selectedAddon.pricing[quantityIndex]) {
- const currentOption = selectedAddon.pricing[quantityIndex];
- const monthlyPerUnitCost = currentOption.monthlyPerUnit;
- const yearlyPerUnitCost = currentOption.yearlyPerUnit;
- baseTotal = isYearly
- ? yearlyPerUnitCost * currentOption.units
- : monthlyPerUnitCost * currentOption.units;
- }
+ } else if (selectedAddon.pricing && selectedAddon.pricing[quantityIndex]) {
+ const currentOption = selectedAddon.pricing[quantityIndex];
+ const monthlyPerUnitCost = currentOption.monthlyPerUnit;
+ const yearlyPerUnitCost = currentOption.yearlyPerUnit;
+ baseTotal = isYearly
+ ? yearlyPerUnitCost * currentOption.units
+ : monthlyPerUnitCost * currentOption.units;
}
let subAddOnTotal = 0;
if (selectedAddon?.id === "academy" && selectedAddon.subAddOns) {
- selectedAddon.subAddOns.forEach(subAddOn => {
+ selectedAddon.subAddOns.forEach((subAddOn) => {
if (selectedSubAddOns[subAddOn.id] && subAddOn.id !== "academy-theory") {
const subAddOnPricing = subAddOn.pricing && subAddOn.pricing[quantityIndex];
if (subAddOnPricing) {
@@ -115,34 +148,59 @@ export const PricingAddons = ({ isYearly = false, setIsYearly, currency, enterpr
});
}
+
const enterpriseUsersCost = (isYearly ? enterprisePlan.yearlyprice : enterprisePlan.monthlyprice) * (enterpriseUsers > 0 ? enterpriseUsers : 1);
setTotalPrice(baseTotal + subAddOnTotal + enterpriseUsersCost);
} else {
setTotalPrice(0);
}
- }, [selectedAddon, quantity, quantityIndex, selectedSubAddOns, isYearly, enterpriseUsers, enterprisePlan]);
+ }, [selectedAddon, quantityIndex, selectedSubAddOns, isYearly, enterpriseUsers, enterprisePlan]);
+
+ const getDefaultAddonState = (addon) => ({
+ quantityIndex: 0,
+ selectedSubAddOns: addon?.id === "academy" ? { "academy-theory": true } : {},
+ });
const handleAddonChange = (addonId) => {
const addon = addOns.find((a) => a.id === addonId);
- setSelectedAddon(addon || null);
- setQuantityIndex(0);
- // Always select "academy-theory" if academy is chosen
- if (addon?.id === "academy") {
- setSelectedSubAddOns({ "academy-theory": true });
- } else {
- setSelectedSubAddOns({});
+ if (selectedAddon) {
+ setPerAddonState((prev) => ({
+ ...prev,
+ [selectedAddon.id]: { quantityIndex, selectedSubAddOns },
+ }));
}
+
+ setSelectedAddon(addon || null);
+ setAddonMenuOpen(false);
+
+ if (!addon) return;
+
+ const restored = perAddonState[addon.id] || getDefaultAddonState(addon);
+ setQuantityIndex(restored.quantityIndex ?? 0);
+ setSelectedSubAddOns(restored.selectedSubAddOns ?? {});
};
const handleSubAddOnToggle = (subAddOnId, isChecked) => {
- setSelectedSubAddOns(prev => ({
- ...prev,
- [subAddOnId]: isChecked
- }));
+ setSelectedSubAddOns((prev) => {
+ const next = { ...prev, [subAddOnId]: isChecked };
+ // keep cache updated for current addon
+ if (selectedAddon) {
+ setPerAddonState((cache) => ({
+ ...cache,
+ [selectedAddon.id]: {
+ ...(cache[selectedAddon.id] || getDefaultAddonState(selectedAddon)),
+ quantityIndex,
+ selectedSubAddOns: next,
+ },
+ }));
+ }
+ return next;
+ });
};
+
const getPlanLinkForAcademy = () => {
if (!selectedAddon || selectedAddon.id !== "academy") {
return { link: "#", name: "Subscribe" };
@@ -151,6 +209,11 @@ export const PricingAddons = ({ isYearly = false, setIsYearly, currency, enterpr
let targetSubAddon = null;
if (selectedSubAddOns["academy-practical"]) {
+ targetSubAddon = selectedAddon.subAddOns?.find((sub) => sub.id === "academy-practical");
+ targetSubAddonName = "with Practical Learning";
+ } else {
+ targetSubAddon = selectedAddon.subAddOns?.find((sub) => sub.id === "academy-theory");
+ targetSubAddonName = "";
targetSubAddon = selectedAddon.subAddOns?.find(sub => sub.id === "academy-practical");
} else {
targetSubAddon = selectedAddon.subAddOns?.find(sub => sub.id === "academy-theory");
@@ -164,13 +227,15 @@ export const PricingAddons = ({ isYearly = false, setIsYearly, currency, enterpr
const currentLearnerCount = targetSubAddon.pricing[quantityIndex].learners;
const matchingPlanLink = targetSubAddon.planLink.find(
- plan => plan.cadence === currentCadence && plan.learners === currentLearnerCount
+ (plan) => plan.cadence === currentCadence && plan.learners === currentLearnerCount
);
if (matchingPlanLink) {
+ const enterpriseUserSeats = enterpriseUsers > 0 ? ` and ${enterpriseUsers} enterprise
+user${enterpriseUsers > 1 ? "s" : ""}` : "";
return {
link: matchingPlanLink.link,
- name: "Subscribe"
+ name: `Subscribe (${currentLearnerCount} learners${targetSubAddonName ? " " + targetSubAddonName : ""}${enterpriseUserSeats})`
};
}
@@ -184,13 +249,36 @@ export const PricingAddons = ({ isYearly = false, setIsYearly, currency, enterpr
return {
link: "#",
- name: "Subscribe"
+ name: `Subscribe to ${selectedAddon.name}`
};
};
return (
+ {addonMenuOpen && (
+ setAddonMenuOpen(false)}
+ onWheel={(e) => {
+ e.preventDefault(); e.stopPropagation();
+ }}
+ onTouchMove={(e) => {
+ e.preventDefault(); e.stopPropagation();
+ }}
+ onScroll={(e) => {
+ e.preventDefault(); e.stopPropagation();
+ }}
+ sx={{
+ position: "fixed",
+ inset: 0,
+ zIndex: (t) => ((t?.zIndex?.menu ?? 1200) - 1),
+ backgroundColor: "transparent",
+ pointerEvents: "auto",
+ touchAction: "none",
+ }}
+ />
+ )}
+
handleAddonChange(e.target.value)}
label="Optionally, choose one or more add-ons"
+ onOpen={() => setAddonMenuOpen(true)}
+ onClose={() => setAddonMenuOpen(false)}
MenuProps={{
- disableScrollLock: true,
- disablePortal: true,
+ disablePortal: false,
+ PaperProps: {
+ sx: { maxHeight: "60vh" },
+ onScrollCapture: (e) => e.stopPropagation(),
+ },
}}
>
{addOns.map((addon) => (
@@ -245,7 +338,7 @@ export const PricingAddons = ({ isYearly = false, setIsYearly, currency, enterpr
{addon.name}
-
+
{addon.id === "academy"
? addon.description
: (() => {
@@ -271,10 +364,14 @@ export const PricingAddons = ({ isYearly = false, setIsYearly, currency, enterpr
handleSubAddOnToggle("academy-theory", e.target.checked)}
- color="primary" />}
+ control={
+ handleSubAddOnToggle("academy-theory", e.target.checked)}
+ color="primary"
+ />
+ }
sx={formControlStyles.base}
/>
- {selectedAddon?.subAddOns?.find(sub => sub.id === "academy-theory")?.features?.map((feature, index) => (
+ {selectedAddon?.subAddOns?.find((sub) => sub.id === "academy-theory")?.features?.map((feature, index) => (
}
@@ -298,10 +395,14 @@ export const PricingAddons = ({ isYearly = false, setIsYearly, currency, enterpr
handleSubAddOnToggle("academy-practical", e.target.checked)}
- color="primary" />}
+ control={
+ handleSubAddOnToggle("academy-practical", e.target.checked)}
+ color="primary"
+ />
+ }
sx={formControlStyles.base}
/>
- {selectedAddon?.subAddOns?.find(sub => sub.id === "academy-practical")?.features?.map((feature, index) => (
+ {selectedAddon?.subAddOns?.find((sub) => sub.id === "academy-practical")?.features?.map((feature, index) => (
}
@@ -324,25 +425,49 @@ export const PricingAddons = ({ isYearly = false, setIsYearly, currency, enterpr
+
+ {(() => {
+ // Determine which sub-addon to show learner count for
+ let targetSubAddon = null;
+ if (selectedSubAddOns["academy-practical"]) {
+ targetSubAddon = selectedAddon?.subAddOns?.find((sub) => sub.id === "academy-practical");
+ } else {
+ targetSubAddon = selectedAddon?.subAddOns?.find((sub) => sub.id === "academy-theory");
+ }
+ return targetSubAddon?.pricing?.[quantityIndex]?.learners || 0;
+ })()}{" "} Learners
+
setQuantityIndex(newValue)}
+ onChange={(event, newValue) => {
+ setQuantityIndex(newValue);
+ // update cache for current addon
+ if (selectedAddon) {
+ setPerAddonState((prev) => ({
+ ...prev,
+ [selectedAddon.id]: {
+ ...(prev[selectedAddon.id] || getDefaultAddonState(selectedAddon)),
+ quantityIndex: newValue,
+ selectedSubAddOns,
+ },
+ }));
+ }
+ }}
min={0}
valueLabelDisplay="auto"
valueLabelFormat={(value) => {
// Determine which sub-addon to show pricing for
let targetSubAddon = null;
if (selectedSubAddOns["academy-practical"]) {
- targetSubAddon = selectedAddon?.subAddOns?.find(sub => sub.id === "academy-practical");
+ targetSubAddon = selectedAddon?.subAddOns?.find((sub) => sub.id === "academy-practical");
} else {
- targetSubAddon = selectedAddon?.subAddOns?.find(sub => sub.id === "academy-theory");
+ targetSubAddon = selectedAddon?.subAddOns?.find((sub) => sub.id === "academy-theory");
}
if (targetSubAddon?.pricing && targetSubAddon.pricing[value]) {
const option = targetSubAddon.pricing[value];
const pricePerUser = isYearly ? option.yearlyPerUser : option.monthlyPerUser;
- const multiplier = selectedSubAddOns["academy-practical"] ? 2 : 1;
- const totalPrice = pricePerUser * option.learners * multiplier;
+ const totalPrice = pricePerUser * option.learners;
const period = isYearly ? "/year" : "/month";
return `${option.learners} learners - ${formatPrice(totalPrice)}${period}`;
}
@@ -352,9 +477,9 @@ export const PricingAddons = ({ isYearly = false, setIsYearly, currency, enterpr
// Determine which sub-addon to use for max value
let targetSubAddon = null;
if (selectedSubAddOns["academy-practical"]) {
- targetSubAddon = selectedAddon?.subAddOns?.find(sub => sub.id === "academy-practical");
+ targetSubAddon = selectedAddon?.subAddOns?.find((sub) => sub.id === "academy-practical");
} else {
- targetSubAddon = selectedAddon?.subAddOns?.find(sub => sub.id === "academy-theory");
+ targetSubAddon = selectedAddon?.subAddOns?.find((sub) => sub.id === "academy-theory");
}
return (targetSubAddon?.pricing?.length - 1) || 0;
})()}
@@ -364,11 +489,20 @@ export const PricingAddons = ({ isYearly = false, setIsYearly, currency, enterpr
// Determine which sub-addon to show pricing for based on selection
let targetSubAddon = null;
if (selectedSubAddOns["academy-practical"]) {
- targetSubAddon = selectedAddon?.subAddOns?.find(sub => sub.id === "academy-practical");
+ targetSubAddon = selectedAddon?.subAddOns?.find((sub) => sub.id === "academy-practical");
} else {
- targetSubAddon = selectedAddon?.subAddOns?.find(sub => sub.id === "academy-theory");
+ targetSubAddon = selectedAddon?.subAddOns?.find((sub) => sub.id === "academy-theory");
}
-
+ return (
+ targetSubAddon?.pricing?.map((option, index) => ({
+ value: index,
+ label: (
+
+ {option.learners === "2500+" ? "2,500+" : option.learners}
+ ({
value: index,
label: (
@@ -381,6 +515,9 @@ export const PricingAddons = ({ isYearly = false, setIsYearly, currency, enterpr
xs: "0.75rem",
sm: "0.9rem",
}
+ }}>
+ {formatPrice(isYearly ? option.yearlyPerUser : option.monthlyPerUser)}
{targetSubAddon.unitLabelSingular}/{isYearly ? "year" : "month"}
+
}}
>
{formatSliderPrice((option.yearlyPerUser / 12) * (selectedSubAddOns["academy-practical"] ? 2 : 1), currency)}
{targetSubAddon.unitLabelSingular}/month
@@ -397,13 +534,13 @@ export const PricingAddons = ({ isYearly = false, setIsYearly, currency, enterpr
}}>
{formatSliderPrice((isYearly ? option.yearlyPerUser : option.monthlyPerUser) * (selectedSubAddOns["academy-practical"] ? 2 : 1), currency)}
{targetSubAddon.unitLabelSingular}/{isYearly ? "year" : "month"}
-
- ),
- })) || [];
+ ),
+ })) || []
+ );
})()}
/>
-
+
Looking for a plan larger than 2,500 learners? Great! Let us know.
@@ -414,9 +551,25 @@ export const PricingAddons = ({ isYearly = false, setIsYearly, currency, enterpr
{selectedAddon !== null && selectedAddon.id !== "academy" && (
<>
+
+ {selectedAddon.pricing?.[quantityIndex]?.units || 0} {selectedAddon?.unitLabel}
+
setQuantityIndex(newValue)}
+ onChange={(event, newValue) => {
+ setQuantityIndex(newValue);
+ // update cache for current addon
+ if (selectedAddon) {
+ setPerAddonState((prev) => ({
+ ...prev,
+ [selectedAddon.id]: {
+ ...(prev[selectedAddon.id] || getDefaultAddonState(selectedAddon)),
+ quantityIndex: newValue,
+ selectedSubAddOns,
+ },
+ }));
+ }
+ }}
min={0}
max={selectedAddon?.pricing?.length - 1 || 0}
step={null}
@@ -438,6 +591,7 @@ export const PricingAddons = ({ isYearly = false, setIsYearly, currency, enterpr
{option.units}
+ {formatPrice(isYearly ? option.yearlyPerUnit * option.units : option.monthlyPerUnit * option.units)}
{formatSliderPrice(isYearly ? option.yearlyPerUnit * option.units : option.monthlyPerUnit * option.units, currency)}
@@ -446,20 +600,19 @@ export const PricingAddons = ({ isYearly = false, setIsYearly, currency, enterpr
/>
>
- )}
+ )}
{selectedAddon && (
-
- Add-on × Quantity / per Subscription Duration
+ Add-on × Quantity / per Subscription Duration
SUBTOTAL
@@ -468,39 +621,30 @@ export const PricingAddons = ({ isYearly = false, setIsYearly, currency, enterpr
-
- {selectedAddon?.id === "academy"
- ? `Theoretical Learning × ${selectedAddon?.subAddOns?.find(sub => sub.id === "academy-theory")?.pricing?.[quantityIndex]?.learners || 0}`
- : `${selectedAddon?.name} × ${selectedAddon?.pricing?.[quantityIndex]?.units || 0}`
- }
-
-
- {(() => {
- if (selectedAddon?.id === "academy") {
- const theorySubAddon = selectedAddon?.subAddOns?.find(sub => sub.id === "academy-theory");
- if (theorySubAddon?.pricing && theorySubAddon.pricing[quantityIndex]) {
- const currentLearnerOption = theorySubAddon.pricing[quantityIndex];
- const monthlyPerUserCost = currentLearnerOption.monthlyPerUser;
- const yearlyPerUserCost = currentLearnerOption.yearlyPerUser;
- const totalCost = isYearly
- ? yearlyPerUserCost * currentLearnerOption.learners
- : monthlyPerUserCost * currentLearnerOption.learners;
- return formatPrice(totalCost);
- }
- return formatPrice(0);
- } else {
- if (selectedAddon?.pricing && selectedAddon.pricing[quantityIndex]) {
- const currentOption = selectedAddon.pricing[quantityIndex];
- const monthlyPerUnitCost = currentOption.monthlyPerUnit;
- const yearlyPerUnitCost = currentOption.yearlyPerUnit;
- const totalCost = isYearly
- ? yearlyPerUnitCost * currentOption.units
- : monthlyPerUnitCost * currentOption.units;
- return formatPrice(totalCost);
- }
- return formatPrice(0);
+
+ {selectedAddon?.id === "academy" ?
+ `Theoretical Learning × ${selectedAddon?.subAddOns?.find(sub => sub.id === "academy-theory")?.pricing?.[quantityIndex]?.learners || 0}` :
+ `${selectedAddon?.name} × ${quantity} x ${selectedAddon?.cadence}`
+ }
+
+
+ {(() => {
+ if (selectedAddon?.id === "academy") {
+ const theorySubAddon = selectedAddon?.subAddOns?.find(sub => sub.id === "academy-theory");
+ if (theorySubAddon?.pricing && theorySubAddon.pricing[quantityIndex]) {
+ const currentLearnerOption = theorySubAddon.pricing[quantityIndex];
+ const monthlyPerUserCost = currentLearnerOption.monthlyPerUser;
+ const yearlyPerUserCost = currentLearnerOption.yearlyPerUser;
+ const totalCost = isYearly
+ ? yearlyPerUserCost * currentLearnerOption.learners
+ : monthlyPerUserCost * currentLearnerOption.learners;
+ return formatPrice(totalCost);
+ }
+ return formatPrice(0);
+ } else {
+ return formatPrice((isYearly ? selectedAddon?.yearlyPrice : selectedAddon?.monthlyPrice) * quantity);
}
- })()}/{isYearly ? "yearly" : "monthly"}
+ })()}
@@ -508,7 +652,7 @@ export const PricingAddons = ({ isYearly = false, setIsYearly, currency, enterpr
selectedSubAddOns[subAddOn.id] && subAddOn.id !== "academy-theory" && (
- {subAddOn.name} × {subAddOn.pricing?.[quantityIndex]?.learners || 0}
+ {subAddOn.name} × {subAddOn.pricing?.[quantityIndex]?.learners || 0}/{isYearly ? "yearly" : "monthly"}
{(
@@ -548,7 +692,9 @@ export const PricingAddons = ({ isYearly = false, setIsYearly, currency, enterpr
{formatPrice((isYearly ? enterprisePlan.yearlyprice : enterprisePlan.monthlyprice) * (enterpriseUsers > 0 ? enterpriseUsers : 1))}/{isYearly ? "yearly" : "monthly"}
-
+
+ TOTAL
+
Total Cost
@@ -580,4 +726,4 @@ export const PricingAddons = ({ isYearly = false, setIsYearly, currency, enterpr
);
-};
\ No newline at end of file
+};