diff --git a/src/courseware/course/sequence/AccessLock/AccessLock.jsx b/src/courseware/course/sequence/AccessLock/AccessLock.jsx
new file mode 100644
index 0000000000..5eec9248f5
--- /dev/null
+++ b/src/courseware/course/sequence/AccessLock/AccessLock.jsx
@@ -0,0 +1,59 @@
+import React, { useCallback } from 'react';
+import PropTypes from 'prop-types';
+import { useNavigate } from 'react-router-dom';
+import { faCheck } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faLock } from '@fortawesome/free-solid-svg-icons';
+import { useIntl, FormattedMessage } from '@edx/frontend-platform/i18n';
+import CompleteIcon from '../sequence-navigation/CompleteIcon';
+import { Button } from '@openedx/paragon';
+import messages from './messages';
+
+const AccessLock = ({
+ courseId
+}) => {
+ const intl = useIntl();
+ const handleClick = () => {};
+
+ const Check = ;
+ const DisplayPrice = () => {
+ return (<>
+ {intl.formatMessage(messages['learn.accessLock.upgrade.buttonText'])}
+ $118.15
+ ($139)
+ >)
+ };
+ return (
+ <>
+
+
+ {intl.formatMessage(messages['learn.accessLock.content.locked'])}
+
+
+ {intl.formatMessage(messages['learn.accessLock.upgrade'])}
+
+
+
+
+ {intl.formatMessage(messages['learn.accessLock.upgrade.header'])}
+
+
+ - {Check}{intl.formatMessage(messages['learn.accessLock.upgrade.benefitOne'])}
+ - {Check}{intl.formatMessage(messages['learn.accessLock.upgrade.benefitTwo'])}
+ - {Check}{intl.formatMessage(messages['learn.accessLock.upgrade.benefitThree'])}
+ - {Check}{intl.formatMessage(messages['learn.accessLock.upgrade.benefitFour'])}
+
+
+
+
+ >
+ );
+};
+AccessLock.propTypes = {
+ courseId: PropTypes.string.isRequired,
+};
+export default AccessLock;
diff --git a/src/courseware/course/sequence/AccessLock/AccessLock.test.jsx b/src/courseware/course/sequence/AccessLock/AccessLock.test.jsx
new file mode 100644
index 0000000000..76b8e69734
--- /dev/null
+++ b/src/courseware/course/sequence/AccessLock/AccessLock.test.jsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import {
+ render, screen, fireEvent, initializeMockApp,
+} from '../../../../setupTest';
+import AssignmentLock from './AccessLock';
+
+const mockNavigate = jest.fn();
+
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useNavigate: () => mockNavigate,
+}));
+
+describe('Assignment Lock', () => {
+ const mockData = {
+ courseId: 'test-course-id',
+ prereqSectionName: 'test-prerequisite-section-name',
+ prereqId: 'test-prerequisite-id',
+ sequenceTitle: 'test-sequence-title',
+ };
+
+ beforeAll(async () => {
+ await initializeMockApp();
+ });
+
+ it('displays sequence title along with lock icon', () => {
+ const { container } = render(, { wrapWithRouter: true });
+
+ const lockIcon = container.querySelector('svg');
+ expect(lockIcon).toHaveClass('fa-lock');
+ expect(lockIcon.parentElement).toHaveTextContent(mockData.sequenceTitle);
+ });
+
+});
diff --git a/src/courseware/course/sequence/AccessLock/index.js b/src/courseware/course/sequence/AccessLock/index.js
new file mode 100644
index 0000000000..0152b5ac43
--- /dev/null
+++ b/src/courseware/course/sequence/AccessLock/index.js
@@ -0,0 +1 @@
+export { default } from './AccessLock';
diff --git a/src/courseware/course/sequence/AccessLock/messages.ts b/src/courseware/course/sequence/AccessLock/messages.ts
new file mode 100644
index 0000000000..6786f8ea92
--- /dev/null
+++ b/src/courseware/course/sequence/AccessLock/messages.ts
@@ -0,0 +1,46 @@
+import { defineMessages } from '@edx/frontend-platform/i18n';
+
+const messages = defineMessages({
+ 'learn.accessLock.content.locked': {
+ id: 'learn.accessLock.content.locked',
+ defaultMessage: 'The content you are trying to access is locked',
+ description: 'Message shown to indicate that a piece of content is unavailable and has a prerequisite.',
+ },
+ 'learn.accessLock.upgrade': {
+ id: 'learn.accessLock.upgrade',
+ defaultMessage: "Upgrade to gain access to locked features like this one and get the most out of your course.",
+ description: '',
+ },
+ 'learn.accessLock.upgrade.header': {
+ id: 'learn.accessLock.upgrade.header',
+ defaultMessage: 'When you upgrade, you get:',
+ description: '',
+ },
+ 'learn.accessLock.upgrade.benefitOne': {
+ id: 'learn.accessLock.upgrade.benefitOne',
+ defaultMessage: 'Feedback and graded assignments',
+ description: '',
+ },
+ 'learn.accessLock.upgrade.benefitTwo': {
+ id: 'learn.accessLock.upgrade.benefitTwo',
+ defaultMessage: 'Shareable certificate upon completion',
+ description: '',
+ },
+ 'learn.accessLock.upgrade.benefitThree': {
+ id: 'learn.accessLock.upgrade.benefitThree',
+ defaultMessage: 'Lifetime access to course materials',
+ description: '',
+ },
+ 'learn.accessLock.upgrade.benefitFour': {
+ id: 'learn.accessLock.upgrade.benefitFour',
+ defaultMessage: 'Instructor support',
+ description: '',
+ },
+ 'learn.accessLock.upgrade.buttonText': {
+ id: 'learn.accessLock.upgrade.buttonText',
+ defaultMessage: 'Upgrade for ',
+ description: '',
+ },
+});
+
+export default messages;
diff --git a/src/courseware/course/sequence/Unit/UnitSuspense.jsx b/src/courseware/course/sequence/Unit/UnitSuspense.jsx
index 63203ef7b3..a9a81adb9b 100644
--- a/src/courseware/course/sequence/Unit/UnitSuspense.jsx
+++ b/src/courseware/course/sequence/Unit/UnitSuspense.jsx
@@ -6,6 +6,7 @@ import { useIntl } from '@edx/frontend-platform/i18n';
import { useModel } from '@src/generic/model-store';
import PageLoading from '@src/generic/PageLoading';
import { GatedUnitContentMessageSlot } from '../../../../plugin-slots/GatedUnitContentMessageSlot';
+import { AccessLockContentMessageSlot } from '../../../../plugin-slots/AccessLockContentMessageSlot';
import messages from '../messages';
import HonorCode from '../honor-code';
@@ -26,9 +27,9 @@ const UnitSuspense = ({
return (
<>
- {shouldDisplayContentGating && (
+ {(shouldDisplayContentGating || unit.accessRestricted) && (
}>
-
+ {unit.accessRestricted ? : }
)}
{shouldDisplayHonorCode && (
diff --git a/src/courseware/data/utils.js b/src/courseware/data/utils.js
index eaf65c46ed..820d4acca9 100644
--- a/src/courseware/data/utils.js
+++ b/src/courseware/data/utils.js
@@ -144,6 +144,7 @@ export function normalizeSequenceMetadata(sequence) {
contentType: unit.type,
graded: unit.graded,
containsContentTypeGatedContent: unit.contains_content_type_gated_content,
+ accessRestricted: unit?.access_restricted || false
})),
};
}
diff --git a/src/plugin-slots/AccessLockContentMessageSlot/README.md b/src/plugin-slots/AccessLockContentMessageSlot/README.md
new file mode 100644
index 0000000000..7a1bf7526a
--- /dev/null
+++ b/src/plugin-slots/AccessLockContentMessageSlot/README.md
@@ -0,0 +1,70 @@
+# Gated Unit Content Message Slot
+
+### Slot ID: `org.openedx.frontend.learning.access_lock_content_message.v1`
+
+### Slot ID Aliases
+* `access_lock_content_message_slot`
+
+### Props:
+* `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.
+
+
+
+```js
+import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
+
+const config = {
+ pluginSlots: {
+ 'org.openedx.frontend.learning.access_lock_content_message.v1': {
+ plugins: [
+ {
+ op: PLUGIN_OPERATIONS.Insert,
+ widgetId: 'default_contents',
+ widget: {
+ id: 'custom_gated_message',
+ type: DIRECT_PLUGIN,
+ RenderWidget: ({ courseId }) => (
+
+
+
+
+
Premium Content
+
This content is available to verified learners only.
+
+
+
+
+ Upgrade your enrollment for course {courseId} to access:
+
+
+ - ✅ Graded assignments and quizzes
+ - 🏆 Verified certificate upon completion
+ - 💬 Full discussion forum access
+ - 📱 Mobile app offline access
+
+
+
+ ),
+ },
+ },
+ ]
+ }
+ },
+}
+
+export default config;
+```
diff --git a/src/plugin-slots/AccessLockContentMessageSlot/index.tsx b/src/plugin-slots/AccessLockContentMessageSlot/index.tsx
new file mode 100644
index 0000000000..ff9c4454f5
--- /dev/null
+++ b/src/plugin-slots/AccessLockContentMessageSlot/index.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+
+import { PluginSlot } from '@openedx/frontend-plugin-framework';
+import AccessLock from '../../courseware/course/sequence/AccessLock';
+
+
+export const AccessLockContentMessageSlot = ({
+ courseId,
+} : AccessLockContentMessageSlotProps) => (
+
+
+
+);
+
+interface AccessLockContentMessageSlotProps {
+ courseId: string;
+}
diff --git a/src/plugin-slots/README.md b/src/plugin-slots/README.md
index be410164d2..96ae708848 100644
--- a/src/plugin-slots/README.md
+++ b/src/plugin-slots/README.md
@@ -11,6 +11,7 @@
* [`org.openedx.frontend.learning.course_outline_tab_notifications.v1`](./CourseOutlineTabNotificationsSlot/)
* [`org.openedx.frontend.learning.course_recommendations.v1`](./CourseRecommendationsSlot/)
* [`org.openedx.frontend.learning.gated_unit_content_message.v1`](./GatedUnitContentMessageSlot/)
+* [`org.openedx.frontend.learning.access_lock_content_message.v1`](./AccessLockContentMessageSlot/)
* [`org.openedx.frontend.learning.next_unit_top_nav_trigger.v1`](./NextUnitTopNavTriggerSlot/)
* [`org.openedx.frontend.learning.notification_tray.v1`](./NotificationTraySlot/)
* [`org.openedx.frontend.learning.notification_widget.v1`](./NotificationWidgetSlot/)