From d1b7ac8e4553a4a8f80552100a6455c9d1e218eb Mon Sep 17 00:00:00 2001 From: Hamzah Ullah Date: Wed, 25 Sep 2024 12:58:18 -0400 Subject: [PATCH] feat: update course about sidebar to take into account usd fixed price --- .../hooks/useCourseRedemptionEligibility.js | 19 +++++++++++- src/components/course/CourseSidebarPrice.jsx | 29 +++++++++++++------ src/components/course/data/hooks.jsx | 22 +++++++++----- src/components/course/data/utils.jsx | 18 ++++++++++-- .../dashboard/SubscriptionExpirationModal.jsx | 14 +++++---- src/utils/common.js | 5 ++++ 6 files changed, 81 insertions(+), 26 deletions(-) diff --git a/src/components/app/data/hooks/useCourseRedemptionEligibility.js b/src/components/app/data/hooks/useCourseRedemptionEligibility.js index 89e6ab42d5..fd7049016c 100644 --- a/src/components/app/data/hooks/useCourseRedemptionEligibility.js +++ b/src/components/app/data/hooks/useCourseRedemptionEligibility.js @@ -6,6 +6,23 @@ import { queryCanRedeem } from '../queries'; import useEnterpriseCustomer from './useEnterpriseCustomer'; import useLateEnrollmentBufferDays from './useLateEnrollmentBufferDays'; +const getContentListPrice = ({ courseRuns }) => { + console.log(courseRuns); + const flatContentPrice = courseRuns.flatMap(run => run.listPrice?.usd).filter(x => !!x); + console.log(flatContentPrice); + // Find the max and min prices + if (!flatContentPrice.length) { + return null; + } + const maxPrice = Math.max(...flatContentPrice); + const minPrice = Math.min(...flatContentPrice); + // Heuristic for displaying the price as a range or a singular price based on runs + if (maxPrice !== minPrice) { + return [minPrice, maxPrice]; + } + return [flatContentPrice[0]]; +}; + export function transformCourseRedemptionEligibility({ courseMetadata, canRedeemData, @@ -17,7 +34,7 @@ export function transformCourseRedemptionEligibility({ const otherSubsidyAccessPolicy = canRedeemData.find( r => r.redeemableSubsidyAccessPolicy, )?.redeemableSubsidyAccessPolicy; - const listPrice = redeemabilityForActiveCourseRun?.listPrice?.usd; + const listPrice = getContentListPrice({ courseRuns: canRedeemData }); const hasSuccessfulRedemption = courseRunKey ? !!canRedeemData.find(r => r.contentKey === courseRunKey)?.hasSuccessfulRedemption : canRedeemData.some(r => r.hasSuccessfulRedemption); diff --git a/src/components/course/CourseSidebarPrice.jsx b/src/components/course/CourseSidebarPrice.jsx index 1dcd9c8b92..1a5c7870c3 100644 --- a/src/components/course/CourseSidebarPrice.jsx +++ b/src/components/course/CourseSidebarPrice.jsx @@ -8,13 +8,27 @@ import { useCoursePrice, useIsCourseAssigned, useUserSubsidyApplicableToCourse, + ZERO_PRICE, } from './data'; import { + ENTERPRISE_OFFER_SUBSIDY_TYPE, LEARNER_CREDIT_SUBSIDY_TYPE, LICENSE_SUBSIDY_TYPE, - ENTERPRISE_OFFER_SUBSIDY_TYPE, useEnterpriseCustomer, } from '../app/data'; +import { sumOfArray } from '../../utils/common'; + +const getContentPriceDisplay = (priceRange) => { + if (!priceRange?.length) { + return numberWithPrecision(ZERO_PRICE); + } + const minPrice = Math.min(...priceRange); + const maxPrice = Math.max(...priceRange); + if (maxPrice !== minPrice) { + return `${numberWithPrecision(minPrice)} - ${numberWithPrecision(maxPrice)}`; + } + return numberWithPrecision(priceRange.sort((a, b) => a - b)[0]); +}; const CourseSidebarPrice = () => { const intl = useIntl(); @@ -23,12 +37,11 @@ const CourseSidebarPrice = () => { const { isCourseAssigned } = useIsCourseAssigned(); const canRequestSubsidy = useCanUserRequestSubsidyForCourse(); const { userSubsidyApplicableToCourse } = useUserSubsidyApplicableToCourse(); - if (!coursePrice) { return ; } - - const originalPriceDisplay = numberWithPrecision(coursePrice.list); + console.log(coursePrice); + const originalPriceDisplay = getContentPriceDisplay(coursePrice.listRange); const showOrigPrice = !enterpriseCustomer.hideCourseOriginalPrice; const crossedOutOriginalPrice = ( @@ -62,8 +75,7 @@ const CourseSidebarPrice = () => { ); } - const hasDiscountedPrice = coursePrice.discounted < coursePrice.list; - + const hasDiscountedPrice = coursePrice.discounted && sumOfArray(coursePrice.discounted) < sumOfArray(coursePrice.listRange); // Case 2: No subsidies found but learner can request a subsidy if (!hasDiscountedPrice && canRequestSubsidy) { return ( @@ -113,14 +125,13 @@ const CourseSidebarPrice = () => { }); } } - const discountedPriceDisplay = `${numberWithPrecision(coursePrice.discounted)} ${currency}`; - + const discountedPriceDisplay = `${getContentPriceDisplay(coursePrice.discounted)} ${currency}`; return ( <>
0 || showOrigPrice })}> {/* discounted > 0 means partial discount */} {showOrigPrice && <>{crossedOutOriginalPrice}{' '}} - {coursePrice.discounted > 0 && ( + {sumOfArray(coursePrice.discounted) > 0 && ( <> individualPrice - (individualPrice * (discountValue / 100)), + ); } if (discountType && discountType.toLowerCase() === SUBSIDY_DISCOUNT_TYPE_MAP.ABSOLUTE.toLowerCase()) { - discountedPrice = Math.max(listPrice - discountValue, 0); + discountedPrice = onlyListPrice.listRange.map( + (individualPrice) => Math.max(individualPrice - discountValue, 0), + ); } if (isDefinedAndNotNull(discountedPrice)) { @@ -226,7 +231,7 @@ export const useCoursePriceForUserSubsidy = ({ } return { ...onlyListPrice, - discounted: onlyListPrice.list, + discounted: onlyListPrice.listRange, }; } @@ -631,6 +636,7 @@ export const useUserSubsidyApplicableToCourse = () => { export function useCoursePrice() { const { data: courseListPrice } = useCourseListPrice(); const { userSubsidyApplicableToCourse } = useUserSubsidyApplicableToCourse(); + console.log(courseListPrice, userSubsidyApplicableToCourse); return useCoursePriceForUserSubsidy({ userSubsidyApplicableToCourse, listPrice: courseListPrice, diff --git a/src/components/course/data/utils.jsx b/src/components/course/data/utils.jsx index c19d769b6c..fa1c3fda64 100644 --- a/src/components/course/data/utils.jsx +++ b/src/components/course/data/utils.jsx @@ -154,6 +154,16 @@ export function getProgramIcon(type) { export const numberWithPrecision = (number, precision = 2) => number.toFixed(precision); +export const formatPrice = (price, options = {}) => { + const USDollar = new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + minimumFractionDigits: 0, + ...options, + }); + return USDollar.format(Math.abs(price)); +}; + /** * * @param couponCodes @@ -817,10 +827,14 @@ export function getEntitlementPrice(entitlements) { * @returns Price for the course run. */ export function getCoursePrice(course) { + console.log(course); + if (course.activeCourseRun?.fixedPriceUsd) { + return [course.activeCourseRun?.fixedPriceUsd]; + } if (course.activeCourseRun?.firstEnrollablePaidSeatPrice) { - return course.activeCourseRun.firstEnrollablePaidSeatPrice; + return [course.activeCourseRun?.firstEnrollablePaidSeatPrice]; } - return getEntitlementPrice(course.entitlements); + return [course.entitlements]; } /** diff --git a/src/components/dashboard/SubscriptionExpirationModal.jsx b/src/components/dashboard/SubscriptionExpirationModal.jsx index 4977f1bafc..e470a8ea1d 100644 --- a/src/components/dashboard/SubscriptionExpirationModal.jsx +++ b/src/components/dashboard/SubscriptionExpirationModal.jsx @@ -25,10 +25,14 @@ const SubscriptionExpirationModal = () => { } = useContext(AppContext); const intl = useIntl(); - const [isOpen, , close] = useToggle(true); - const { data: enterpriseCustomer } = useEnterpriseCustomer(); const { data: subscriptions } = useSubscriptions(); const { subscriptionPlan, subscriptionLicense } = subscriptions; + const seenExpiredSubscriptionModal = !!global.localStorage.getItem( + EXPIRED_SUBSCRIPTION_MODAL_LOCALSTORAGE_KEY(subscriptionLicense), + ); + console.log(seenExpiredSubscriptionModal); + const [isOpen, , close] = useToggle(!seenExpiredSubscriptionModal); + const { data: enterpriseCustomer } = useEnterpriseCustomer(); const { daysUntilExpirationIncludingRenewals, expirationDate, @@ -88,10 +92,6 @@ const SubscriptionExpirationModal = () => { close(); global.localStorage.setItem(EXPIRED_SUBSCRIPTION_MODAL_LOCALSTORAGE_KEY(subscriptionLicense), 'true'); }; - - const seenExpiredSubscriptionModal = !!global.localStorage.getItem( - EXPIRED_SUBSCRIPTION_MODAL_LOCALSTORAGE_KEY(subscriptionLicense), - ); // If the subscription has already expired, we show a different un-dismissible modal if (!isCurrent) { if (seenExpiredSubscriptionModal) { @@ -109,6 +109,7 @@ const SubscriptionExpirationModal = () => { )} hasCloseButton + onClose={handleSubscriptionExpiredModalDismissal} >

Your organization's access to your subscription has expired. You will only have audit @@ -170,6 +171,7 @@ const SubscriptionExpirationModal = () => { )} hasCloseButton + onClose={handleSubscriptionExpiringModalDismissal} >

Your organization's access to your current subscription is expiring in diff --git a/src/utils/common.js b/src/utils/common.js index 84774b0b28..e4a8f999e9 100644 --- a/src/utils/common.js +++ b/src/utils/common.js @@ -25,6 +25,9 @@ export const isNull = (value) => { }; export const isDefinedAndNotNull = (value) => { + if (Array.isArray(value)) { + return value.every(item => isDefined(item) && !isNull(item)); + } const values = createArrayFromValue(value); return values.every(item => isDefined(item) && !isNull(item)); }; @@ -39,6 +42,8 @@ export const hasTruthyValue = (value) => { return values.every(item => !!item); }; +export const sumOfArray = (values) => values.reduce((prev, next) => prev + next, 0); + export const hasValidStartExpirationDates = ({ startDate, expirationDate, endDate }) => { const now = dayjs(); // Subscriptions use "expirationDate" while Codes use "endDate"