Skip to content

Commit

Permalink
feat: subsidy box points to learner credit
Browse files Browse the repository at this point in the history
added tests

added more tests

minor fixes
  • Loading branch information
Sameen Fatima authored and Sameen Fatima committed Aug 30, 2023
1 parent d0c44cc commit 512b2db
Show file tree
Hide file tree
Showing 11 changed files with 493 additions and 5 deletions.
83 changes: 83 additions & 0 deletions src/components/dashboard/sidebar/LearnerCreditSummaryCard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Badge, Row, Col } from '@edx/paragon';
import dayjs from 'dayjs';
import {
LEARNER_CREDIT_SUMMARY_CARD_TITLE,
LEARNER_CREDIT_ACTIVE_BADGE_LABEL,
LEARNER_CREDIT_ACTIVE_BADGE_VARIANT,
LEARNER_CREDIT_CARD_SUMMARY,
} from './data/constants';
import SidebarCard from './SidebarCard';

const LearnerCreditSummaryCard = ({
className, subsidyLearnerCredit, searchCoursesCta,
}) => {
const totalRemainingBalanceForUser = subsidyLearnerCredit.reduce(
(accumulator, currentSubsidy) => accumulator + (currentSubsidy.remainingBalancePerUser || 0),
0,
);
const subsidyExpiringFirst = subsidyLearnerCredit.sort(
(a, b) => new Date(a.subsidyExpirationDate) - new Date(b.subsidyExpirationDate),
)[0];
return (
<SidebarCard
title={
(
<div className="d-flex align-items-center justify-content-between">
<h3 className="m-0">{LEARNER_CREDIT_SUMMARY_CARD_TITLE}</h3>
<Badge
variant={LEARNER_CREDIT_ACTIVE_BADGE_VARIANT}
className="ml-2"
data-testid="learner-credit-status-badge"
>
{LEARNER_CREDIT_ACTIVE_BADGE_LABEL}
</Badge>
</div>
)
}
cardClassNames={className}
>
{totalRemainingBalanceForUser
? (
<p data-testid="learner-credit-text-detailed">
Apply your <b>${totalRemainingBalanceForUser}</b>{' '}
balance to enroll into courses.
</p>
)
: (
<p data-testid="learner-credit-summary-text">
{ LEARNER_CREDIT_CARD_SUMMARY }
</p>
)}

{subsidyExpiringFirst.subsidyExpirationDate && (
<p data-testid="learner-credit-summary-end-date-text">
Available until <b>{dayjs(subsidyExpiringFirst.subsidyExpirationDate).format('MMM D, YYYY')}</b>
</p>
)}

{searchCoursesCta && (
<Row className="mt-3 d-flex justify-content-end">
<Col xl={12}>{searchCoursesCta}</Col>
</Row>
)}
</SidebarCard>
);
};

LearnerCreditSummaryCard.propTypes = {
subsidyLearnerCredit: PropTypes.arrayOf(PropTypes.shape({
subsidyExpirationDate: PropTypes.string,
remainingBalancePerUser: PropTypes.number,
})).isRequired,
className: PropTypes.string,
searchCoursesCta: PropTypes.node,
};

LearnerCreditSummaryCard.defaultProps = {
className: undefined,
searchCoursesCta: undefined,
};

export default LearnerCreditSummaryCard;
13 changes: 11 additions & 2 deletions src/components/dashboard/sidebar/SubsidiesSummary.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import classNames from 'classnames';
import CouponCodesSummaryCard from './CouponCodesSummaryCard';
import SubscriptionSummaryCard from './SubscriptionSummaryCard';
import EnterpriseOffersSummaryCard from './EnterpriseOffersSummaryCard';
import LearnerCreditSummaryCard from './LearnerCreditSummaryCard';
import { UserSubsidyContext } from '../../enterprise-user-subsidy';
import { LICENSE_STATUS } from '../../enterprise-user-subsidy/data/constants';
import { CATALOG_ACCESS_CARD_BUTTON_TEXT } from './data/constants';
Expand Down Expand Up @@ -35,6 +36,7 @@ const SubsidiesSummary = ({
couponCodes: { couponCodesCount },
enterpriseOffers,
canEnrollWithEnterpriseOffers,
subsidyLearnerCredit,
} = useContext(UserSubsidyContext);

const {
Expand All @@ -54,9 +56,10 @@ const SubsidiesSummary = ({
&& userSubscriptionLicense?.status === LICENSE_STATUS.ACTIVATED) || licenseRequests.length > 0;

const hasAssignedCodesOrCodeRequests = couponCodesCount > 0 || couponCodeRequests.length > 0;
const hasAvailableLearnerCredit = subsidyLearnerCredit?.length > 0;

const hasAvailableSubsidyOrRequests = hasActiveLicenseOrLicenseRequest
|| hasAssignedCodesOrCodeRequests || canEnrollWithEnterpriseOffers;
|| hasAssignedCodesOrCodeRequests || canEnrollWithEnterpriseOffers || hasAvailableLearnerCredit;

if (!hasAvailableSubsidyOrRequests) {
return null;
Expand Down Expand Up @@ -100,12 +103,18 @@ const SubsidiesSummary = ({
className="border-0 shadow-none"
/>
)}
{canEnrollWithEnterpriseOffers && (
{!hasAvailableLearnerCredit && canEnrollWithEnterpriseOffers && (
<EnterpriseOffersSummaryCard
className="border-0 shadow-none"
offers={enterpriseOffers}
/>
)}
{ hasAvailableLearnerCredit && (
<LearnerCreditSummaryCard
className="border-0 shadow-none"
subsidyLearnerCredit={subsidyLearnerCredit}
/>
)}
</div>
{searchCoursesCta && (
<SidebarCard
Expand Down
6 changes: 6 additions & 0 deletions src/components/dashboard/sidebar/data/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ export const ENTERPRISE_OFFER_ACTIVE_BADGE_LABEL = 'Active';
export const ENTERPRISE_OFFER_ACTIVE_BADGE_VARIANT = 'success';
export const ENTERPRISE_OFFER_SUMMARY_CARD_SUMMARY = 'Apply your organization\'s learner credit balance to enroll into courses with no out of pocket cost.';

// LearnerCreditSummaryCard
export const LEARNER_CREDIT_SUMMARY_CARD_TITLE = 'Learner Credit';
export const LEARNER_CREDIT_ACTIVE_BADGE_LABEL = 'Active';
export const LEARNER_CREDIT_ACTIVE_BADGE_VARIANT = 'success';
export const LEARNER_CREDIT_CARD_SUMMARY = 'Apply your organization\'s learner credit balance to enroll into courses with no out of pocket cost.';

// Dashboard Sidebar texts
export const CATALOG_ACCESS_CARD_BUTTON_TEXT = 'Find a course';
export const NEED_HELP_BLOCK_TITLE = 'Need help?';
Expand Down
36 changes: 35 additions & 1 deletion src/components/dashboard/sidebar/tests/DashboardSidebar.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
LICENSE_REQUESTED_NOTICE,
COUPON_CODES_SUMMARY_NOTICE,
ENTERPRISE_OFFER_SUMMARY_CARD_TITLE,
LEARNER_CREDIT_SUMMARY_CARD_TITLE,
} from '../data/constants';
import { LICENSE_STATUS } from '../../../enterprise-user-subsidy/data/constants';
import CourseEnrollmentsContextProvider from '../../main-content/course-enrollments/CourseEnrollmentsContextProvider';
Expand Down Expand Up @@ -61,6 +62,7 @@ describe('<DashboardSidebar />', () => {
couponCodesCount: 0,
},
enterpriseOffers: [],
subsidyLearnerCredit: undefined,
};
const initialAppState = {
enterpriseConfig: {
Expand Down Expand Up @@ -173,7 +175,7 @@ describe('<DashboardSidebar />', () => {
);
expect(screen.queryByText(SUBSCRIPTION_SUMMARY_CARD_TITLE)).toBeFalsy();
});
test('Enterprise offers summary card is displayed when enterprise has active offers and no subscriptions or coupons', () => {
test('Enterprise offers summary card is displayed when enterprise has active offers and no subscriptions or coupons or learner credit', () => {
renderWithRouter(
<DashboardSidebarWithContext
initialAppState={initialAppState}
Expand Down Expand Up @@ -219,6 +221,38 @@ describe('<DashboardSidebar />', () => {
expect(screen.queryByText(SUBSCRIPTION_SUMMARY_CARD_TITLE)).not.toBeInTheDocument();
expect(screen.queryByText(COUPON_CODES_SUMMARY_NOTICE)).toBeInTheDocument();
});

test('Learner credit summary card is displayed when enterprise has learner credit', () => {
renderWithRouter(
<DashboardSidebarWithContext
initialAppState={initialAppState}
initialUserSubsidyState={{
...defaultUserSubsidyState,
subsidyLearnerCredit: [{ remainingBalancePerUser: 5, subsidyExpirationDate: '2030-01-01 12:00:00Z' }],
}}
/>,
);
expect(screen.queryByText(LEARNER_CREDIT_SUMMARY_CARD_TITLE)).toBeInTheDocument();
});

test('Only learner credit summary card is displayed when enterprise has both; learner credit and offers', () => {
renderWithRouter(
<DashboardSidebarWithContext
initialAppState={initialAppState}
initialUserSubsidyState={{
...defaultUserSubsidyState,
subsidyLearnerCredit: [{ remainingBalancePerUser: 5, subsidyExpirationDate: '2030-01-01 12:00:00Z' }],
enterpriseOffers: [{
uuid: 'enterprise-offer-id',
}],
canEnrollWithEnterpriseOffers: true,
}}
/>,
);
expect(screen.queryByTestId('learner-credit-text-detailed')).toBeInTheDocument();
expect(screen.queryByTestId('offer-summary-text-detailed')).toBeFalsy();
});

test('Find a course button is not rendered when user has no coupon codes or license subsidy', () => {
renderWithRouter(
<DashboardSidebarWithContext
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React from 'react';
import '@testing-library/jest-dom/extend-expect';
import { screen, render } from '@testing-library/react';
import LearnerCreditSummaryCard from '../LearnerCreditSummaryCard';
import { LEARNER_CREDIT_SUMMARY_CARD_TITLE } from '../data/constants';

const mockLearnerCreditSubsidy = {
uuid: 'test-subsidy-uuid',
catalogUuid: 'test-enterprise-catalog-uuid',
subsidyExpirationDate: '2023-06-01T00:00:00Z',
remainingBalancePerUser: null,
};

const mockSubsidyWithRemainingBalance = {
...mockLearnerCreditSubsidy,
remainingBalancePerUser: 200,
};

describe('<LearnerCreditSummaryCard />', () => {
it('should render searchCoursesCta', () => {
const cta = 'Search Courses';
render(
<LearnerCreditSummaryCard
subsidyLearnerCredit={[mockLearnerCreditSubsidy]}
searchCoursesCta={
<button type="button">{cta}</button>
}
/>,
);

expect(screen.getByText(LEARNER_CREDIT_SUMMARY_CARD_TITLE)).toBeInTheDocument();
expect(screen.getByText(cta)).toBeInTheDocument();
});

it('should render default summary text if remainingBalancePerUser is null', () => {
render(
<LearnerCreditSummaryCard
subsidyLearnerCredit={[mockLearnerCreditSubsidy]}
/>,
);

expect(screen.getByTestId('learner-credit-summary-text')).toBeInTheDocument();
});

it('should render detailed summary text if remainingBalancePerUser is not null', () => {
const subsidiesWithCredit = [
mockSubsidyWithRemainingBalance,
{
...mockSubsidyWithRemainingBalance,
remainingBalancePerUser: 100,
},
];
render(
<LearnerCreditSummaryCard
subsidyLearnerCredit={subsidiesWithCredit}
/>,
);
// calculate sum of balance available for user across all subsidies
const totalRemainingBalance = subsidiesWithCredit.reduce((acc, subsidy) => {
if (subsidy.remainingBalancePerUser) {
return acc + subsidy.remainingBalancePerUser;
}
return acc;
}, 0);
expect(screen.getByTestId('learner-credit-text-detailed')).toBeInTheDocument();
expect(screen.getByText(`$${totalRemainingBalance}`)).toBeInTheDocument();
});

it('should render earliest subsidy end date, if applicable', () => {
render(
<LearnerCreditSummaryCard
subsidyLearnerCredit={[
mockLearnerCreditSubsidy,
{
...mockLearnerCreditSubsidy,
subsidyExpirationDate: '2022-04-01T00:00:00Z', // earliest end date
},
]}
/>,
);

expect(screen.getByTestId('learner-credit-summary-end-date-text')).toBeInTheDocument();
expect(screen.getByText('2022', { exact: false })).toBeInTheDocument();
});
});
8 changes: 7 additions & 1 deletion src/components/enterprise-user-subsidy/UserSubsidy.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { LoadingSpinner } from '../loading-spinner';
import {
useCouponCodes,
useSubscriptions,
useSubsidyLearnerCredit,
} from './data/hooks';
import { useEnterpriseOffers } from './enterprise-offers/data/hooks';
import { LOADING_SCREEN_READER_TEXT } from './data/constants';
Expand All @@ -18,6 +19,7 @@ export const UserSubsidyContext = createContext();
const UserSubsidy = ({ children }) => {
const { enterpriseConfig, authenticatedUser } = useContext(AppContext);

const { userId } = authenticatedUser;
// Subscriptions
const {
customerAgreementConfig,
Expand All @@ -28,6 +30,7 @@ const UserSubsidy = ({ children }) => {
activateUserLicense,
} = useSubscriptions({ enterpriseConfig, authenticatedUser });

const [subsidyLearnerCredit, isLoadingLearnerCredit] = useSubsidyLearnerCredit(enterpriseConfig.uuid, userId);
// Coupon Codes
const [couponCodes, isLoadingCouponCodes] = useCouponCodes(enterpriseConfig.uuid);

Expand All @@ -50,10 +53,11 @@ const UserSubsidy = ({ children }) => {
isLoadingSubscriptions,
isLoadingCouponCodes,
isLoadingEnterpriseOffers,
isLoadingLearnerCredit,
];
return loadingStates.includes(true);
},
[isLoadingSubscriptions, isLoadingCouponCodes, isLoadingEnterpriseOffers],
[isLoadingSubscriptions, isLoadingCouponCodes, isLoadingEnterpriseOffers, isLoadingLearnerCredit],
);

const contextValue = useMemo(
Expand All @@ -72,6 +76,7 @@ const UserSubsidy = ({ children }) => {
showExpirationNotifications,
customerAgreementConfig,
activateUserLicense,
subsidyLearnerCredit,
};
},
[
Expand All @@ -86,6 +91,7 @@ const UserSubsidy = ({ children }) => {
showExpirationNotifications,
customerAgreementConfig,
activateUserLicense,
subsidyLearnerCredit,
],
);

Expand Down
26 changes: 26 additions & 0 deletions src/components/enterprise-user-subsidy/data/hooks/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { LICENSE_STATUS } from '../constants';
import {
fetchSubscriptionLicensesForUser,
fetchCustomerAgreementData,
fetchSubsidyLearnerCredit,
requestAutoAppliedLicense,
activateLicense,
} from '../service';
Expand Down Expand Up @@ -244,3 +245,28 @@ export function useCustomerAgreementData(enterpriseId) {

return [customerAgreement, isLoading];
}

export function useSubsidyLearnerCredit(enterpriseId, userID) {
const [learnerCreditSubsidies, setLearnerCreditSubsidies] = useState();
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
fetchSubsidyLearnerCredit(enterpriseId, userID)
.then((response) => {
const results = camelCaseObject(response.data);
if (results.length) {
setLearnerCreditSubsidies(results);
} else {
setLearnerCreditSubsidies(null);

Check warning on line 260 in src/components/enterprise-user-subsidy/data/hooks/hooks.js

View check run for this annotation

Codecov / codecov/patch

src/components/enterprise-user-subsidy/data/hooks/hooks.js#L259-L260

Added lines #L259 - L260 were not covered by tests
}
})
.catch((error) => {
logError(new Error(error));
setLearnerCreditSubsidies(null);

Check warning on line 265 in src/components/enterprise-user-subsidy/data/hooks/hooks.js

View check run for this annotation

Codecov / codecov/patch

src/components/enterprise-user-subsidy/data/hooks/hooks.js#L263-L265

Added lines #L263 - L265 were not covered by tests
})
.finally(() => {
setIsLoading(false);
});
}, [enterpriseId, userID]);
return [learnerCreditSubsidies, isLoading];
}
Loading

0 comments on commit 512b2db

Please sign in to comment.