Compare commits

...

4 Commits

Author SHA1 Message Date
Jacobo Dominguez
db0a56566f docs: add comprehensive readme documentation for plugin slots (#1770) 2025-08-06 16:41:36 +05:30
Brian Smith
b78e78313b fix(docs): correct ProgressCertificateStatusSlot README title (#1689) 2025-07-21 13:11:29 -04:00
Brian Smith
29ec85ddbe fix(docs): correct CourseRecommendationsSlot README title (#1688) 2025-07-21 13:11:29 -04:00
Maxim Beder
241e188465 feat: update certificate icons
Old certificates icons contained edX trademark logos, which were not
suitable for the open source repos. Replaced with icons that contain
Open edX logos.
2025-05-05 20:43:01 +05:30
19 changed files with 380 additions and 20 deletions

View File

@@ -18,8 +18,8 @@ import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import CelebrationMobile from './assets/celebration_456x328.gif';
import CelebrationDesktop from './assets/celebration_750x540.gif';
import certificate from '../../../generic/assets/edX_certificate.png';
import certificateLocked from '../../../generic/assets/edX_locked_certificate.png';
import certificate from '../../../generic/assets/openedx_certificate.png';
import certificateLocked from '../../../generic/assets/openedx_locked_certificate.png';
import { FormattedPricing } from '../../../generic/upgrade-button';
import messages from './messages';
import { useModel } from '../../../generic/model-store';

View File

@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import { getConfig } from '@edx/frontend-platform';
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
import { Alert, Button, Hyperlink } from '@openedx/paragon';
import certImage from '../../../generic/assets/edX_certificate.png';
import certImage from '../../../generic/assets/openedx_certificate.png';
import messages from './messages';
/**

View File

@@ -9,7 +9,7 @@ import {
import { Locked } from '@openedx/paragon/icons';
import SidebarContext from '../../sidebar/SidebarContext';
import messages from './messages';
import certificateLocked from '../../../../generic/assets/edX_locked_certificate.png';
import certificateLocked from '../../../../generic/assets/openedx_locked_certificate.png';
import { useModel } from '../../../../generic/model-store';
import { UpgradeButton } from '../../../../generic/upgrade-button';
import {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -1,4 +1,4 @@
# Content iframe Loader Slot
# Content IFrame Loader Slot
### Slot ID: `org.openedx.frontend.learning.content_iframe_loader.v1`
@@ -6,5 +6,51 @@
* `content_iframe_loader_slot`
### Props:
* `courseId`
* `defaultLoaderComponent`
* `courseId` - String identifier for the current course
* `defaultLoaderComponent` - React component used as the default loading indicator
## Description
This slot is used to customize the loading indicator displayed while course content is being loaded in an iframe. It appears when content is loading but hasn't fully rendered yet, providing a customizable loading experience for learners.
The default implementation shows a `PageLoading` component with a screen reader message.
## Example
The following `env.config.jsx` will replace the default loading spinner with a custom loading component that shows the course ID and a custom message.
![Content Iframe Loader Example](./images/loader-example.png)
```js
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
const config = {
pluginSlots: {
'org.openedx.frontend.learning.content_iframe_loader.v1': {
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widgetId: 'default_contents',
widget: {
id: 'custom_iframe_loader',
type: DIRECT_PLUGIN,
RenderWidget: ({ courseId, defaultLoaderComponent }) => (
<div style={{ textAlign: 'center', padding: '2rem' }}>
<h3>Loading course content...</h3>
<p>Course: {courseId}</p>
<div style={{ margin: '1rem 0' }}>
{defaultLoaderComponent}
</div>
<p>Please wait while we prepare your learning experience</p>
</div>
),
},
},
]
}
},
}
export default config;
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

View File

@@ -6,5 +6,55 @@
* `outline_tab_notifications_slot`
### Props:
* `courseId`
* `model`
* `courseId` - String identifier for the current course
* `model` - String indicating the context model (set to 'outline')
## Description
This slot is used to add custom notification components to the course outline tab sidebar. It appears in the right sidebar of the course outline/home tab, positioned between the Course Tools widget and the Course Dates widget.
The slot provides a flexible way to inject custom notifications, announcements, or informational components that are contextually relevant to the course outline view.
## Example
The following `env.config.jsx` will add a custom notification component to the course outline tab sidebar.
![Course notification example](./images/course-outline-notification-example.png)
```js
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
const config = {
pluginSlots: {
'org.openedx.frontend.learning.course_outline_tab_notifications.v1': {
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_outline_notification',
type: DIRECT_PLUGIN,
RenderWidget: ({ courseId, model }) => (
<div className="card mb-3">
<div className="card-body">
<h5 className="card-title">📢 Course Announcement</h5>
<p className="card-text">
Important updates for course {courseId}
</p>
<p className="text-muted small">
Context: {model}
</p>
<button className="btn btn-primary btn-sm">
View Details
</button>
</div>
</div>
),
},
},
]
}
},
}
export default config;
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

View File

@@ -1,4 +1,4 @@
# Unit Title Slot
# Course Recommendations Slot
### Slot ID: `org.openedx.frontend.learning.course_recommendations.v1`

View File

@@ -6,4 +6,65 @@
* `gated_unit_content_message_slot`
### Props:
* `courseId`
* `courseId` - String identifier for the current course
## Description
This slot is used to customize the message displayed when course content is gated or locked for learners who haven't upgraded to a verified track. It appears when a unit contains content that requires a paid enrollment (such as graded assignments) and the learner is on the audit track.
The default implementation shows a `LockPaywall` component that displays an upgrade message with benefits of upgrading, including access to graded assignments, certificates, and full course features.
This slot is conditionally rendered only when `contentTypeGatingEnabled` is true and the unit `containsContentTypeGatedContent`.
## Example
The following `env.config.jsx` will replace the default paywall message with a custom gated content component.
![Gated unit message example](./images/gated-unit-message-example.png)
```js
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
const config = {
pluginSlots: {
'org.openedx.frontend.learning.gated_unit_content_message.v1': {
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widgetId: 'default_contents',
widget: {
id: 'custom_gated_message',
type: DIRECT_PLUGIN,
RenderWidget: ({ courseId }) => (
<div className="alert alert-warning" role="alert">
<div className="d-flex align-items-center mb-3">
<i className="fa fa-lock fa-2x me-3" aria-hidden="true"></i>
<div>
<h4 className="alert-heading mb-1">Premium Content</h4>
<p className="mb-0">This content is available to verified learners only.</p>
</div>
</div>
<hr />
<p className="mb-3">
Upgrade your enrollment for course {courseId} to access:
</p>
<ul className="mb-3">
<li> Graded assignments and quizzes</li>
<li>🏆 Verified certificate upon completion</li>
<li>💬 Full discussion forum access</li>
<li>📱 Mobile app offline access</li>
</ul>
<button className="btn btn-success">
Upgrade Now
</button>
</div>
),
},
},
]
}
},
}
export default config;
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

View File

@@ -6,6 +6,92 @@
* `notification_tray_slot`
### Props:
* `courseId`
* `notificationCurrentState`
* `setNotificationCurrentState`
* `courseId` - String identifier for the current course
* `model` - String indicating the context model (set to 'coursewareMeta')
* `notificationCurrentState` - Current state of upgrade notifications (UpgradeNotificationState)
* `setNotificationCurrentState` - Function to update the notification state
## Description
This slot is used to customize the notification tray that appears in the courseware sidebar. The notification tray displays upgrade-related notifications and alerts for learners in verified mode courses. It provides a way to show contextual notifications about course access, deadlines, and upgrade opportunities.
The slot is conditionally rendered only for learners in verified mode courses. For non-verified courses, a simple "no notifications" message is displayed instead.
The `notificationCurrentState` can be one of: `'accessLastHour'`, `'accessHoursLeft'`, `'accessDaysLeft'`, `'FPDdaysLeft'`, `'FPDLastHour'`, `'accessDateView'`, or `'PastExpirationDate'`.
## Example
The following `env.config.jsx` will customize the notification tray with additional notification types and styling.
![Notification tray slot example](./images/notification-tray-slot-example.png)
```js
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
const config = {
pluginSlots: {
'org.openedx.frontend.learning.notification_tray.v1': {
plugins: [
{
// Insert custom notification content
op: PLUGIN_OPERATIONS.Replace,
widget: {
id: 'custom_notifications',
type: DIRECT_PLUGIN,
RenderWidget: ({
courseId,
model,
notificationCurrentState,
setNotificationCurrentState
}) => (
<div className="p-3">
<h5 className="mb-3">📬 Course Notifications</h5>
{notificationCurrentState === 'accessLastHour' && (
<div className="alert alert-warning mb-3">
<strong> Last Chance!</strong>
<p className="mb-0">Your access expires in less than an hour.</p>
</div>
)}
{notificationCurrentState === 'accessDaysLeft' && (
<div className="alert alert-info mb-3">
<strong>📅 Access Reminder</strong>
<p className="mb-0">Your course access expires in a few days.</p>
</div>
)}
<div className="notification-item p-2 mb-2 border rounded">
<div className="d-flex justify-content-between align-items-center">
<span>📚 New course material available</span>
<button
className="btn btn-sm btn-outline-primary"
onClick={() => setNotificationCurrentState('accessDateView')}
>
View
</button>
</div>
</div>
<div className="notification-item p-2 mb-2 border rounded">
<div className="d-flex justify-content-between align-items-center">
<span>🎯 Assignment due tomorrow</span>
<span className="badge bg-warning">Due Soon</span>
</div>
</div>
<div className="text-center mt-3">
<small className="text-muted">
Course: {courseId} | Model: {model}
</small>
</div>
</div>
),
},
},
]
}
},
}
export default config;
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

View File

@@ -6,8 +6,125 @@
* `notification_widget_slot`
### Props:
* `courseId`
* `model`
* `notificationCurrentState`
* `setNotificationCurrentState`
* `toggleSidebar`
* `courseId` - String identifier for the current course
* `model` - String indicating the context model (set to 'coursewareMeta')
* `notificationCurrentState` - Current state of upgrade notifications (UpgradeNotificationState)
* `setNotificationCurrentState` - Function to update the notification state
* `toggleSidebar` - Function to toggle the sidebar open/closed
## Description
This slot is used to customize the notification widget that appears in the discussions-notifications sidebar. The widget is displayed as a compact notification component that shows upgrade-related alerts and can trigger the full notification tray when clicked.
The widget appears in the combined discussions-notifications sidebar and is conditionally rendered based on the `hideNotificationbar` and `isNotificationbarAvailable` flags. It automatically tracks user engagement and calls `onNotificationSeen` after a 3-second timeout.
The `notificationCurrentState` can be one of: `'accessLastHour'`, `'accessHoursLeft'`, `'accessDaysLeft'`, `'FPDdaysLeft'`, `'FPDLastHour'`, `'accessDateView'`, or `'PastExpirationDate'`.
## Example
The following `env.config.jsx` will customize the notification widget with a more interactive design and additional functionality.
![Notification widget slot example](./images/notification-widget-slot-example.png)
```js
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
const NotificationWidget = ({
courseId,
model,
notificationCurrentState,
setNotificationCurrentState,
toggleSidebar
}) => {
const getNotificationContent = () => {
switch (notificationCurrentState) {
case 'accessLastHour':
return {
icon: '⚠️',
title: 'Final Hour!',
message: 'Access expires in less than 1 hour',
variant: 'danger'
};
case 'accessHoursLeft':
return {
icon: '⏰',
title: 'Expiring Soon',
message: 'Access expires in a few hours',
variant: 'warning'
};
case 'accessDaysLeft':
return {
icon: '📅',
title: 'Access Reminder',
message: 'Access expires in a few days',
variant: 'info'
};
case 'FPDdaysLeft':
case 'FPDLastHour':
return {
icon: '🎯',
title: 'Upgrade Available',
message: 'Get full access to premium features',
variant: 'primary'
};
default:
return {
icon: '🔔',
title: 'Notifications',
message: 'Click to view updates',
variant: 'secondary'
};
}
};
const notification = getNotificationContent();
return (
<div
className={`alert alert-${notification.variant} mb-0 cursor-pointer`}
onClick={toggleSidebar}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
toggleSidebar();
}
}}
>
<div className="d-flex align-items-center">
<span className="me-2" style={{ fontSize: '1.2em' }}>
{notification.icon}
</span>
<div className="flex-fill">
<strong className="d-block">{notification.title}</strong>
<small>{notification.message}</small>
</div>
<div className="ms-2">
<i className="fa fa-chevron-right" aria-hidden="true"></i>
</div>
</div>
</div>
);
}
const config = {
pluginSlots: {
'org.openedx.frontend.learning.notification_widget.v1': {
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widgetId: 'default_contents',
widget: {
id: 'custom_notification_widget',
type: DIRECT_PLUGIN,
RenderWidget: NotificationWidget
},
},
]
}
},
}
export default config;
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View File

@@ -1,4 +1,4 @@
# Unit Title Slot
# Progress Certificate Status Slot
### Slot ID: `org.openedx.frontend.learning.progress_certificate_status.v1`