Skip to content

Commit 8c9280d

Browse files
authored
feat: adds learner access section (#1467)
* feat: adds learner access section
1 parent 8d86eed commit 8c9280d

30 files changed

+630
-91
lines changed

src/components/BulkEnrollmentPage/BulkEnrollmentContext.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {
1+
import React, {
22
createContext, useState, useReducer, useMemo,
33
} from 'react';
44
import PropTypes from 'prop-types';

src/components/BulkEnrollmentPage/stepper/AddCoursesStep.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { screen, waitFor } from '@testing-library/react';
22
import '@testing-library/jest-dom/extend-expect';
3-
import { useMemo } from 'react';
3+
import React, { useMemo } from 'react';
44
import { IntlProvider } from '@edx/frontend-platform/i18n';
55
import { Provider } from 'react-redux';
66
import configureMockStore from 'redux-mock-store';

src/components/BulkEnrollmentPage/stepper/ReviewStepCourseList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useContext, useEffect, useMemo } from 'react';
1+
import React, { useContext, useEffect, useMemo } from 'react';
22
import PropTypes from 'prop-types';
33
import { InstantSearch, Configure, connectStateResults } from 'react-instantsearch-dom';
44
import { camelCaseObject } from '@edx/frontend-platform';

src/components/ContentHighlights/ContentHighlightsContext.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useMemo, useState } from 'react';
1+
import React, { useMemo, useState } from 'react';
22
import { createContext } from 'use-context-selector';
33
import { SearchClient } from 'algoliasearch/lite';
44

src/components/ContentHighlights/HighlightStepper/HighlightStepperSelectContentSearch.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useMemo, useState } from 'react';
1+
import React, { useMemo, useState } from 'react';
22
import PropTypes from 'prop-types';
33
import { useContextSelector } from 'use-context-selector';
44
import { Configure, connectStateResults, InstantSearch } from 'react-instantsearch-dom';

src/components/ContentHighlights/tests/ContentHighlights.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import React from 'react';
12
import { screen } from '@testing-library/react';
23
import '@testing-library/jest-dom/extend-expect';
34
import { Provider } from 'react-redux';

src/components/EnterpriseApp/EnterpriseAppContextProvider.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import React from 'react';
12
import {
23
render,
34
waitFor,
Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,46 @@
11
import PropTypes from 'prop-types';
22
import { Skeleton } from '@openedx/paragon';
33
import EnrollmentCard from './EnrollmentCard';
4-
import useEnterpriseCourseEnrollments from '../data/hooks/useEnterpriseCourseEnrollments';
54

6-
const CourseEnrollments = ({ userEmail, lmsUserId, enterpriseUuid }) => {
7-
const { isLoading, data } = useEnterpriseCourseEnrollments({ userEmail, lmsUserId, enterpriseUuid });
8-
const enrollments = data?.data.enrollments;
5+
const CourseEnrollments = ({ enrollments, isLoading }) => (
6+
<div className="ml-5">
7+
{isLoading && !enrollments ? (
8+
<Skeleton
9+
width={400}
10+
height={200}
11+
/>
12+
) : (
13+
<>
14+
<h3>Enrollments</h3>
15+
{enrollments?.completed?.map((enrollment) => (
16+
<EnrollmentCard enrollment={enrollment} />
17+
))}
18+
{enrollments?.inProgress?.map((enrollment) => (
19+
<EnrollmentCard enrollment={enrollment} />
20+
))}
21+
{enrollments?.upcoming?.map((enrollment) => (
22+
<EnrollmentCard enrollment={enrollment} />
23+
))}
24+
</>
25+
)}
26+
</div>
27+
);
28+
29+
const enrollmentShape = PropTypes.shape({
30+
courseKey: PropTypes.string,
31+
courseType: PropTypes.string,
32+
courseRunStatus: PropTypes.string,
33+
displayName: PropTypes.string,
34+
orgName: PropTypes.string,
35+
}).isRequired;
936

10-
return (
11-
<div className="ml-5">
12-
{isLoading && !enrollments ? (
13-
<Skeleton
14-
width={400}
15-
height={200}
16-
/>
17-
) : (
18-
<>
19-
<h3>Enrollments</h3>
20-
{enrollments?.completed?.map((enrollment) => (
21-
<EnrollmentCard enrollment={enrollment} />
22-
))}
23-
{enrollments?.inProgress?.map((enrollment) => (
24-
<EnrollmentCard enrollment={enrollment} />
25-
))}
26-
{enrollments?.upcoming?.map((enrollment) => (
27-
<EnrollmentCard enrollment={enrollment} />
28-
))}
29-
</>
30-
)}
31-
</div>
32-
);
33-
};
3437
CourseEnrollments.propTypes = {
35-
userEmail: PropTypes.string.isRequired,
36-
lmsUserId: PropTypes.string.isRequired,
37-
enterpriseUuid: PropTypes.string.isRequired,
38+
enrollments: PropTypes.shape({
39+
completed: PropTypes.arrayOf(enrollmentShape),
40+
inProgress: PropTypes.arrayOf(enrollmentShape),
41+
upcoming: PropTypes.arrayOf(enrollmentShape),
42+
}).isRequired,
43+
isLoading: PropTypes.bool.isRequired,
3844
};
3945

4046
export default CourseEnrollments;
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { useParams } from 'react-router-dom';
4+
import {
5+
Hyperlink, Icon, Stack, Skeleton,
6+
} from '@openedx/paragon';
7+
import { NorthEast } from '@openedx/paragon/icons';
8+
import { useIntl } from '@edx/frontend-platform/i18n';
9+
import { subscriptionPageUrl, learnerCreditPageUrl } from '../utils';
10+
11+
type SubsidyLinkProps = {
12+
subscription: Subscription;
13+
};
14+
15+
const SubsidyLink = ({ subscription }: SubsidyLinkProps) => {
16+
const { enterpriseSlug } = useParams() as { enterpriseSlug: string };
17+
const { subscriptionPlan: { planType, title, uuid } } = subscription;
18+
const subscriptionUrl = subscriptionPageUrl({ enterpriseSlug, uuid });
19+
20+
return (
21+
<div className="pl-3">
22+
<div className="d-flex align-items-center">
23+
<Hyperlink
24+
className="font-weight-bold pb-2 text-truncate d-flex align-items-center"
25+
style={{ maxWidth: '90%' }}
26+
destination={subscriptionUrl}
27+
target="_blank"
28+
showLaunchIcon={false}
29+
>
30+
<span className="text-truncate">{title}</span>
31+
<Icon
32+
id="SampleIcon"
33+
size="xs"
34+
src={NorthEast}
35+
screenReaderText="Visit subscription page"
36+
className="ml-1 mb-1"
37+
/>
38+
</Hyperlink>
39+
</div>
40+
<p className="small pb-2">{planType}</p>
41+
</div>
42+
);
43+
};
44+
45+
SubsidyLink.propTypes = {
46+
subscription: PropTypes.shape({
47+
uuid: PropTypes.string.isRequired,
48+
subscriptionPlan: PropTypes.shape({
49+
planType: PropTypes.string.isRequired,
50+
title: PropTypes.string.isRequired,
51+
uuid: PropTypes.string.isRequired,
52+
}).isRequired,
53+
}).isRequired,
54+
};
55+
56+
type LearnerCreditLinkProps = {
57+
plan: LearnerCreditPlan;
58+
};
59+
60+
const LearnerCreditLink = ({ plan }: LearnerCreditLinkProps) => {
61+
const { displayName, uuid } = plan;
62+
const { enterpriseSlug } = useParams() as { enterpriseSlug: string };
63+
const learnerCreditUrl = learnerCreditPageUrl({ enterpriseSlug, uuid });
64+
const intl = useIntl();
65+
66+
const policyTypeText = intl.formatMessage({
67+
id: 'adminPortal.peopleManagement.learnerDetailPage.policyType',
68+
defaultMessage: plan.policyType === 'AssignedLearnerCreditAccessPolicy' ? 'Assignment' : 'Browse & Enroll',
69+
description: 'Text indicating the type of learner credit policy',
70+
});
71+
72+
return (
73+
<div className="pl-3">
74+
<div className="d-flex align-items-center">
75+
<Hyperlink
76+
className="font-weight-bold pb-2 text-truncate d-flex align-items-center"
77+
style={{ maxWidth: '90%' }}
78+
destination={learnerCreditUrl}
79+
target="_blank"
80+
showLaunchIcon={false}
81+
>
82+
<span className="text-truncate">{displayName}</span>
83+
<Icon
84+
id="SampleIcon"
85+
size="xs"
86+
src={NorthEast}
87+
screenReaderText="Visit credit plan page"
88+
className="ml-1 mb-1"
89+
/>
90+
</Hyperlink>
91+
</div>
92+
<p className="small pb-2">{policyTypeText}</p>
93+
</div>
94+
);
95+
};
96+
97+
LearnerCreditLink.propTypes = {
98+
plan: PropTypes.shape({
99+
displayName: PropTypes.string.isRequired,
100+
active: PropTypes.bool.isRequired,
101+
policyType: PropTypes.string.isRequired,
102+
uuid: PropTypes.string.isRequired,
103+
}).isRequired,
104+
};
105+
106+
type LearnerAccessProps = {
107+
subscriptions: Subscription[];
108+
creditPlansData: LearnerCreditPlan[];
109+
isLoading: boolean;
110+
};
111+
112+
const LearnerAccess = ({ subscriptions, creditPlansData, isLoading }: LearnerAccessProps) => {
113+
const intl = useIntl();
114+
const accessHeader = intl.formatMessage({
115+
id: 'adminPortal.peopleManagement.learnerDetailPage.accessHeader',
116+
defaultMessage: 'Access',
117+
description: 'Header for learner access information',
118+
});
119+
return (
120+
<div>
121+
{isLoading ? (
122+
<Skeleton
123+
width={400}
124+
height={200}
125+
/>
126+
) : (
127+
<Stack gap={4}>
128+
<div className="pt-3">
129+
<h3 className="pb-3">{accessHeader}</h3>
130+
<div className="learner-detail-section">
131+
{subscriptions.length > 0 && (
132+
<div>
133+
<h5 className="pb-3 ml-3 mb-0">SUBSCRIPTION</h5>
134+
{subscriptions.map((subscription) => (
135+
<SubsidyLink key={subscription.uuid} subscription={subscription} />
136+
))}
137+
</div>
138+
)}
139+
140+
{creditPlansData?.length > 0 && (
141+
<div>
142+
<h5 className="pb-3 ml-3 mb-0">LEARNER CREDIT</h5>
143+
{creditPlansData.map((plan) => (
144+
<LearnerCreditLink key={plan.uuid} plan={plan} />
145+
))}
146+
</div>
147+
)}
148+
</div>
149+
</div>
150+
</Stack>
151+
)}
152+
</div>
153+
);
154+
};
155+
156+
LearnerAccess.propTypes = {
157+
subscriptions: PropTypes.arrayOf(PropTypes.shape({
158+
uuid: PropTypes.string.isRequired,
159+
subscriptionPlan: PropTypes.shape({
160+
planType: PropTypes.string.isRequired,
161+
title: PropTypes.string.isRequired,
162+
}).isRequired,
163+
})).isRequired,
164+
creditPlansData: PropTypes.arrayOf(PropTypes.shape({
165+
displayName: PropTypes.string.isRequired,
166+
active: PropTypes.bool.isRequired,
167+
policyType: PropTypes.string.isRequired,
168+
uuid: PropTypes.string.isRequired,
169+
})).isRequired,
170+
isLoading: PropTypes.bool.isRequired,
171+
};
172+
173+
export default LearnerAccess;

src/components/PeopleManagement/LearnerDetailPage/LearnerDetailGroupMemberships.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import React from 'react';
12
import { useParams } from 'react-router-dom';
23
import PropTypes from 'prop-types';
34
import {

0 commit comments

Comments
 (0)