Skip to content

Commit b1ee8a3

Browse files
authored
feat: add course end dashboard plugin slots (#1658)
* feat: add additional course end plugin slots * fix: bring plugin slot names in line with new naming scheme * refactor: change plugin files to tsx,remove propTypes * fixup! refactor: change plugin files to tsx,remove propTypes * fixup! fixup! refactor: change plugin files to tsx,remove propTypes * fixup! fixup! fixup! refactor: change plugin files to tsx,remove propTypes * fix: accidentally committed test code * fix: plugin-slot fixes * chore: add ENTERPRISE_LEARNER_PORTAL_URL env var
1 parent 73406fb commit b1ee8a3

File tree

17 files changed

+206
-66
lines changed

17 files changed

+206
-66
lines changed

.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ ECOMMERCE_BASE_URL=''
1616
ENABLE_JUMPNAV='true'
1717
ENABLE_NOTICES=''
1818
ENTERPRISE_LEARNER_PORTAL_HOSTNAME=''
19+
ENTERPRISE_LEARNER_PORTAL_URL=''
1920
EXAMS_BASE_URL=''
2021
FAVICON_URL=''
2122
IGNORED_ERROR_REGEX=''

.env.development

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ ECOMMERCE_BASE_URL='http://localhost:18130'
1616
ENABLE_JUMPNAV='true'
1717
ENABLE_NOTICES=''
1818
ENTERPRISE_LEARNER_PORTAL_HOSTNAME='localhost:8734'
19+
ENTERPRISE_LEARNER_PORTAL_URL='http://localhost:8734'
1920
EXAMS_BASE_URL=''
2021
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
2122
IGNORED_ERROR_REGEX=''

.env.test

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ ECOMMERCE_BASE_URL='http://localhost:18130'
1616
ENABLE_JUMPNAV='true'
1717
ENABLE_NOTICES=''
1818
ENTERPRISE_LEARNER_PORTAL_HOSTNAME='localhost:8734'
19+
ENTERPRISE_LEARNER_PORTAL_URL='http://localhost:8734'
1920
EXAMS_BASE_URL='http://localhost:18740'
2021
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
2122
IGNORED_ERROR_REGEX=''
@@ -48,3 +49,4 @@ TWITTER_URL='https://twitter.com/edXOnline'
4849
USER_INFO_COOKIE_NAME='edx-user-info'
4950
PRIVACY_POLICY_URL='http://localhost:18000/privacy'
5051
SHOW_UNGRADED_ASSIGNMENT_PROGRESS=''
52+
ENTERPRISE_LEARNER_PORTAL_URL='http://localhost:Enterprise'

src/courseware/course/course-exit/CourseCelebration.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ import messages from './messages';
2525
import { useModel } from '../../../generic/model-store';
2626
import { requestCert } from '../../../course-home/data/thunks';
2727
import ProgramCompletion from './ProgramCompletion';
28-
import DashboardFootnote from './DashboardFootnote';
2928
import UpgradeFootnote from './UpgradeFootnote';
3029
import SocialIcons from '../../social-share/SocialIcons';
3130
import { logClick, logVisit } from './utils';
3231
import { DashboardLink, IdVerificationSupportLink, ProfileLink } from '../../../shared/links';
33-
import CourseRecommendationsSlot from '../../../plugin-slots/CourseRecommendationsSlot';
32+
import DashboardFootnote from './DashboardFootnote';
33+
import { CourseRecommendationsSlot } from '../../../plugin-slots/CourseExitPluginSlots';
3434

3535
const LINKEDIN_BLUE = '#2867B2';
3636

src/courseware/course/course-exit/CourseExit.jsx

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,18 @@
1-
import React, { useEffect } from 'react';
1+
import { useEffect } from 'react';
22

3-
import { getConfig } from '@edx/frontend-platform';
4-
import { useIntl } from '@edx/frontend-platform/i18n';
5-
import { Button } from '@openedx/paragon';
63
import { useSelector } from 'react-redux';
74
import { Navigate } from 'react-router-dom';
85

96
import CourseCelebration from './CourseCelebration';
107
import CourseInProgress from './CourseInProgress';
118
import CourseNonPassing from './CourseNonPassing';
129
import { COURSE_EXIT_MODES, getCourseExitMode } from './utils';
13-
import messages from './messages';
1410
import { unsubscribeFromGoalReminders } from './data/thunks';
11+
import { CourseExitViewCoursesPluginSlot } from '../../../plugin-slots/CourseExitPluginSlots';
1512

1613
import { useModel } from '../../../generic/model-store';
1714

1815
const CourseExit = () => {
19-
const intl = useIntl();
2016
const { courseId } = useSelector(state => state.courseware);
2117
const {
2218
certificateData,
@@ -64,14 +60,7 @@ const CourseExit = () => {
6460

6561
return (
6662
<>
67-
<div className="row w-100 mt-2 mb-4 justify-content-end">
68-
<Button
69-
variant="outline-primary"
70-
href={`${getConfig().LMS_BASE_URL}/dashboard`}
71-
>
72-
{intl.formatMessage(messages.viewCoursesButton)}
73-
</Button>
74-
</div>
63+
<CourseExitViewCoursesPluginSlot />
7564
{body}
7665
</>
7766
);

src/courseware/course/course-exit/DashboardFootnote.jsx

Lines changed: 4 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,19 @@
1-
import React from 'react';
21
import PropTypes from 'prop-types';
3-
import { useSelector } from 'react-redux';
4-
5-
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
6-
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
7-
import { Hyperlink } from '@openedx/paragon';
2+
import { useIntl } from '@edx/frontend-platform/i18n';
83
import { faCalendarAlt } from '@fortawesome/free-regular-svg-icons';
9-
import { getConfig } from '@edx/frontend-platform';
10-
11-
import { useModel } from '../../../generic/model-store';
124

5+
import { DashboardFootnoteLinkPluginSlot } from '../../../plugin-slots/CourseExitPluginSlots';
136
import Footnote from './Footnote';
147
import messages from './messages';
15-
import { logClick } from './utils';
168

179
const DashboardFootnote = ({ variant }) => {
1810
const intl = useIntl();
19-
const { courseId } = useSelector(state => state.courseware);
20-
const { org } = useModel('courseHomeMeta', courseId);
21-
const { administrator } = getAuthenticatedUser();
22-
23-
const dashboardLink = (
24-
<Hyperlink
25-
style={{ textDecoration: 'underline' }}
26-
destination={`${getConfig().LMS_BASE_URL}/dashboard`}
27-
className="text-reset"
28-
onClick={() => logClick(org, courseId, administrator, 'dashboard_footnote', { variant })}
29-
>
30-
{intl.formatMessage(messages.dashboardLink)}
31-
</Hyperlink>
32-
);
11+
const dashboardLink = (<DashboardFootnoteLinkPluginSlot variant={variant} />);
3312

3413
return (
3514
<Footnote
3615
icon={faCalendarAlt}
37-
text={(
38-
<FormattedMessage
39-
id="courseCelebration.dashboardInfo" // for historical reasons
40-
defaultMessage="You can access this course and its materials on your {dashboardLink}."
41-
description="Text that precedes link to learner's dashboard"
42-
values={{ dashboardLink }}
43-
/>
44-
)}
16+
text={intl.formatMessage(messages.dashboardInfo, { dashboardLink })}
4517
/>
4618
);
4719
};

src/courseware/course/course-exit/messages.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ const messages = defineMessages({
7676
defaultMessage: 'Dashboard',
7777
description: 'Link to user’s dashboard',
7878
},
79+
dashboardInfo: {
80+
id: 'courseCelebration.dashboardInfo', // for historical reasons
81+
defaultMessage: 'You can access this course and its materials on your {dashboardLink}.',
82+
description: "Text that precedes link to learner's dashboard",
83+
},
7984
endOfCourseDescription: {
8085
id: 'courseExit.endOfCourseDescription',
8186
defaultMessage: 'Unfortunately, you are not currently eligible for a certificate. You need to receive a passing grade to be eligible for a certificate.',

src/index.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ initialize({
170170
CREDIT_HELP_LINK_URL: process.env.CREDIT_HELP_LINK_URL || null,
171171
DISCUSSIONS_MFE_BASE_URL: process.env.DISCUSSIONS_MFE_BASE_URL || null,
172172
ENTERPRISE_LEARNER_PORTAL_HOSTNAME: process.env.ENTERPRISE_LEARNER_PORTAL_HOSTNAME || null,
173+
ENTERPRISE_LEARNER_PORTAL_URL: process.env.ENTERPRISE_LEARNER_PORTAL_URL || null,
173174
ENABLE_JUMPNAV: process.env.ENABLE_JUMPNAV || null,
174175
ENABLE_NOTICES: process.env.ENABLE_NOTICES || null,
175176
INSIGHTS_BASE_URL: process.env.INSIGHTS_BASE_URL || null,
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Course Exit "View Courses" Button Plugin Slot
2+
3+
### Slot ID: `org.openedx.frontend.learning.course_exit_view_courses.v1`
4+
### Props:
5+
* `content: { href }`
6+
7+
## Description
8+
9+
This slot is used for modifying "View Courses" button in the course exit screen
10+
11+
## Example
12+
13+
The following `env.config.jsx` will make the link link to `example.com`
14+
15+
```js
16+
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
17+
18+
const config = {
19+
pluginSlots: {
20+
'org.openedx.frontend.learning.course_exit_view_courses.v1: {
21+
keepDefault: true,
22+
plugins: [
23+
{
24+
op: PLUGIN_OPERATIONS.Modify,
25+
widgetId: 'default_contents',
26+
fn: (widget) => {
27+
widget.content.href = 'http://www.example.com';
28+
return widget;
29+
}
30+
},
31+
]
32+
},
33+
},
34+
}
35+
36+
export default config;
37+
```
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { Button } from '@openedx/paragon';
2+
import { getConfig } from '@edx/frontend-platform';
3+
import { PluginSlot } from '@openedx/frontend-plugin-framework';
4+
import { useIntl } from '@edx/frontend-platform/i18n';
5+
import messages from '../../../courseware/course/course-exit/messages';
6+
7+
interface Props {
8+
href: string
9+
}
10+
11+
const ViewCoursesLink: React.FC<Props> = ({ href }: Props) => {
12+
const intl = useIntl();
13+
return (
14+
<div className="row w-100 mt-2 mb-4 justify-content-end">
15+
<Button
16+
variant="outline-primary"
17+
href={href}
18+
>
19+
{intl.formatMessage(messages.viewCoursesButton)}
20+
</Button>
21+
</div>
22+
);
23+
};
24+
25+
export const CourseExitViewCoursesPluginSlot: React.FC = () => {
26+
const href = `${getConfig().LMS_BASE_URL}/dashboard`;
27+
return (
28+
<PluginSlot
29+
id="org.openedx.frontend.learning.course_exit_view_courses.v1"
30+
slotOptions={{
31+
mergeProps: true,
32+
}}
33+
>
34+
<ViewCoursesLink href={href} />
35+
</PluginSlot>
36+
);
37+
};
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { PluginSlot } from '@openedx/frontend-plugin-framework';
2+
import CourseRecommendations from '../../../courseware/course/course-exit/CourseRecommendations';
3+
4+
interface Props {
5+
variant: string;
6+
}
7+
8+
export const CourseRecommendationsSlot: React.FC<Props> = ({ variant }: Props) => (
9+
<PluginSlot
10+
id="org.openedx.frontend.learning.course_recommendations.v1"
11+
idAliases={['course_recommendations_slot']}
12+
>
13+
<CourseRecommendations variant={variant} />
14+
</PluginSlot>
15+
);
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Course Exit Dashboard Footnote Link Plugin Slot
2+
3+
### Slot ID: `org.openedx.frontend.learning.course_exit_dashboard_footnote_link.v1`
4+
### Props:
5+
* `variant`
6+
* `content: { destination }`
7+
8+
## Description
9+
10+
This slot is used for modifying the link to the learner dashboard in the footnote on the course exit page
11+
12+
## Example
13+
14+
The following `env.config.jsx` will change the link to point to `example.com`
15+
16+
![Screenshot of modified course celebration](./screenshot_custom.png)
17+
18+
```js
19+
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
20+
21+
const config = {
22+
pluginSlots: {
23+
'org.openedx.frontend.learning.course_exit_dashboard_footnote_link.v1': {
24+
keepDefault: true,
25+
plugins: [
26+
{
27+
op: PLUGIN_OPERATIONS.Modify,
28+
widgetId: 'default_contents',
29+
fn: (widget) => {
30+
widget.content.destination = 'http://www.example.com';
31+
return widget;
32+
}
33+
},
34+
]
35+
},
36+
},
37+
}
38+
39+
export default config;
40+
```
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { Hyperlink } from '@openedx/paragon';
2+
import { getConfig } from '@edx/frontend-platform';
3+
import { PluginSlot } from '@openedx/frontend-plugin-framework';
4+
import { useIntl } from '@edx/frontend-platform/i18n';
5+
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
6+
import messages from '../../../courseware/course/course-exit/messages';
7+
import { logClick } from '../../../courseware/course/course-exit/utils';
8+
import { useModel } from '../../../generic/model-store';
9+
import { useContextId } from '../../../data/hooks';
10+
11+
interface LinkProps {
12+
variant: string;
13+
destination: string;
14+
}
15+
16+
const DashboardFootnoteLink: React.FC<LinkProps> = ({ variant, destination }: LinkProps) => {
17+
const intl = useIntl();
18+
const courseId = useContextId();
19+
const { org } = useModel('courseHomeMeta', courseId);
20+
const { administrator } = getAuthenticatedUser();
21+
return (
22+
<Hyperlink
23+
style={{ textDecoration: 'underline' }}
24+
destination={destination}
25+
className="text-reset"
26+
onClick={() => logClick(org, courseId, administrator, 'dashboard_footnote', { variant })}
27+
>
28+
{intl.formatMessage(messages.dashboardLink)}
29+
</Hyperlink>
30+
);
31+
};
32+
33+
interface PluginProps {
34+
variant: string
35+
}
36+
37+
export const DashboardFootnoteLinkPluginSlot: React.FC = ({ variant }: PluginProps) => {
38+
const destination = `${getConfig().LMS_BASE_URL}/dashboard`;
39+
return (
40+
<PluginSlot
41+
id="org.openedx.frontend.learning.course_exit_dashboard_footnote_link.v1"
42+
slotOptions={{
43+
mergeProps: true,
44+
}}
45+
>
46+
<DashboardFootnoteLink variant={variant} destination={destination} />
47+
</PluginSlot>
48+
);
49+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { DashboardFootnoteLinkPluginSlot } from './DashboardFootnoteLinkPluginSlot';
2+
import { CourseRecommendationsSlot } from './CourseRecommendationsSlot';
3+
import { CourseExitViewCoursesPluginSlot } from './CourseExitViewCoursesPluginSlot';
4+
5+
export {
6+
DashboardFootnoteLinkPluginSlot,
7+
CourseRecommendationsSlot,
8+
CourseExitViewCoursesPluginSlot,
9+
};

src/plugin-slots/CourseRecommendationsSlot/index.jsx

Lines changed: 0 additions & 18 deletions
This file was deleted.

0 commit comments

Comments
 (0)