{
-
+ {blockId && (
+
+ )}
diff --git a/src/course-unit/constants.js b/src/course-unit/constants.ts
similarity index 100%
rename from src/course-unit/constants.js
rename to src/course-unit/constants.ts
diff --git a/src/course-unit/header-navigations/HeaderNavigations.jsx b/src/course-unit/header-navigations/HeaderNavigations.jsx
deleted file mode 100644
index 8fbcd7ddd..000000000
--- a/src/course-unit/header-navigations/HeaderNavigations.jsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import PropTypes from 'prop-types';
-import { useIntl } from '@edx/frontend-platform/i18n';
-import { Button } from '@openedx/paragon';
-import { Edit as EditIcon } from '@openedx/paragon/icons';
-
-import { COURSE_BLOCK_NAMES } from '../../constants';
-import messages from './messages';
-
-const HeaderNavigations = ({ headerNavigationsActions, category }) => {
- const intl = useIntl();
- const { handleViewLive, handlePreview, handleEdit } = headerNavigationsActions;
-
- return (
-
- );
-};
-
-HeaderNavigations.propTypes = {
- headerNavigationsActions: PropTypes.shape({
- handleViewLive: PropTypes.func.isRequired,
- handlePreview: PropTypes.func.isRequired,
- handleEdit: PropTypes.func.isRequired,
- }).isRequired,
- category: PropTypes.string.isRequired,
-};
-
-export default HeaderNavigations;
diff --git a/src/course-unit/header-navigations/HeaderNavigations.test.jsx b/src/course-unit/header-navigations/HeaderNavigations.test.tsx
similarity index 100%
rename from src/course-unit/header-navigations/HeaderNavigations.test.jsx
rename to src/course-unit/header-navigations/HeaderNavigations.test.tsx
diff --git a/src/course-unit/header-navigations/HeaderNavigations.tsx b/src/course-unit/header-navigations/HeaderNavigations.tsx
new file mode 100644
index 000000000..17c726c4e
--- /dev/null
+++ b/src/course-unit/header-navigations/HeaderNavigations.tsx
@@ -0,0 +1,98 @@
+import { useIntl } from '@edx/frontend-platform/i18n';
+import {
+ Button, ButtonGroup, Stack,
+} from '@openedx/paragon';
+import {
+ Add, Edit as EditIcon, FindInPage, InfoOutline,
+} from '@openedx/paragon/icons';
+import { COURSE_BLOCK_NAMES } from '@src/constants';
+
+import messages from './messages';
+import { isUnitPageNewDesignEnabled } from '../utils';
+
+type HeaderNavigationActions = {
+ handleViewLive: () => void;
+ handlePreview: () => void;
+ handleEdit: () => void;
+};
+
+type HeaderNavigationsProps = {
+ headerNavigationsActions: HeaderNavigationActions;
+ category: string;
+};
+
+/**
+ * Generic header navigations to be used in this pages:
+ * - Unit page
+ * - Legacy library content page
+ * - Split test page
+ */
+const HeaderNavigations = ({ headerNavigationsActions, category }: HeaderNavigationsProps) => {
+ const intl = useIntl();
+ const {
+ handleViewLive,
+ handlePreview,
+ handleEdit,
+ } = headerNavigationsActions;
+
+ const showNewDesignButtons = isUnitPageNewDesignEnabled();
+
+ return (
+
+ );
+};
+
+export default HeaderNavigations;
diff --git a/src/course-unit/header-navigations/messages.ts b/src/course-unit/header-navigations/messages.ts
index 53239434a..332896608 100644
--- a/src/course-unit/header-navigations/messages.ts
+++ b/src/course-unit/header-navigations/messages.ts
@@ -16,6 +16,31 @@ const messages = defineMessages({
defaultMessage: 'Edit',
description: 'The unit edit button text',
},
+ addButton: {
+ id: 'course-authoring.course-unit.button.add',
+ defaultMessage: 'Add',
+ description: 'The unit add button text',
+ },
+ moreActionsButtonAriaLabel: {
+ id: 'course-authoring.course-unit.button.more-actions',
+ defaultMessage: 'More actions',
+ description: 'The unit more actions button aria-label',
+ },
+ analyticsMenu: {
+ id: 'course-authoring.course-unit.button.analytics',
+ defaultMessage: 'Analytics',
+ description: 'The unit analytics menu text',
+ },
+ alignMenu: {
+ id: 'course-authoring.course-unit.button.align',
+ defaultMessage: 'Align',
+ description: 'The unit align menu text',
+ },
+ infoButton: {
+ id: 'course-authoring.course-unit.button.unit-info',
+ defaultMessage: 'Unit Info',
+ description: 'The unit info button text',
+ },
});
export default messages;
diff --git a/src/course-unit/header-title/HeaderTitle.jsx b/src/course-unit/header-title/HeaderTitle.jsx
deleted file mode 100644
index 1bcb8a5c9..000000000
--- a/src/course-unit/header-title/HeaderTitle.jsx
+++ /dev/null
@@ -1,113 +0,0 @@
-import { useEffect, useState } from 'react';
-import { useDispatch, useSelector } from 'react-redux';
-import PropTypes from 'prop-types';
-import { useIntl } from '@edx/frontend-platform/i18n';
-import { Form, IconButton, useToggle } from '@openedx/paragon';
-import {
- EditOutline as EditIcon,
- Settings as SettingsIcon,
-} from '@openedx/paragon/icons';
-
-import ConfigureModal from '../../generic/configure-modal/ConfigureModal';
-import { COURSE_BLOCK_NAMES } from '../../constants';
-import { getCourseUnitData } from '../data/selectors';
-import { updateQueryPendingStatus } from '../data/slice';
-import messages from './messages';
-
-const HeaderTitle = ({
- unitTitle,
- isTitleEditFormOpen,
- handleTitleEdit,
- handleTitleEditSubmit,
- handleConfigureSubmit,
-}) => {
- const intl = useIntl();
- const dispatch = useDispatch();
- const [titleValue, setTitleValue] = useState(unitTitle);
- const currentItemData = useSelector(getCourseUnitData);
- const [isConfigureModalOpen, openConfigureModal, closeConfigureModal] = useToggle(false);
- const { selectedPartitionIndex, selectedGroupsLabel } = currentItemData.userPartitionInfo ?? {};
-
- const isXBlockComponent = [
- COURSE_BLOCK_NAMES.libraryContent.id,
- COURSE_BLOCK_NAMES.splitTest.id,
- COURSE_BLOCK_NAMES.component.id,
- ].includes(currentItemData.category);
-
- const onConfigureSubmit = (...arg) => {
- handleConfigureSubmit(currentItemData.id, ...arg, closeConfigureModal);
- };
-
- const getVisibilityMessage = () => {
- let message;
-
- if (selectedPartitionIndex !== -1 && !Number.isNaN(selectedPartitionIndex) && selectedGroupsLabel) {
- message = intl.formatMessage(messages.definedVisibilityMessage, { selectedGroupsLabel });
- } else if (currentItemData.hasPartitionGroupComponents) {
- message = intl.formatMessage(messages.commonVisibilityMessage);
- }
-
- return message ? ({message}
) : null;
- };
-
- useEffect(() => {
- setTitleValue(unitTitle);
- dispatch(updateQueryPendingStatus(true));
- }, [unitTitle]);
-
- return (
- <>
-
- {isTitleEditFormOpen ? (
-
- e && e.focus()}
- value={titleValue}
- name="displayName"
- onChange={(e) => setTitleValue(e.target.value)}
- aria-label={intl.formatMessage(messages.ariaLabelButtonEdit)}
- onBlur={() => handleTitleEditSubmit(titleValue)}
- onKeyDown={(e) => {
- if (e.key === 'Enter') {
- handleTitleEditSubmit(titleValue);
- }
- }}
- />
-
- ) : unitTitle}
-
-
-
-
- {getVisibilityMessage()}
- >
- );
-};
-
-export default HeaderTitle;
-
-HeaderTitle.propTypes = {
- unitTitle: PropTypes.string.isRequired,
- isTitleEditFormOpen: PropTypes.bool.isRequired,
- handleTitleEdit: PropTypes.func.isRequired,
- handleTitleEditSubmit: PropTypes.func.isRequired,
- handleConfigureSubmit: PropTypes.func.isRequired,
-};
diff --git a/src/course-unit/header-title/HeaderTitle.scss b/src/course-unit/header-title/HeaderTitle.scss
index d191b1cfe..9727b99eb 100644
--- a/src/course-unit/header-title/HeaderTitle.scss
+++ b/src/course-unit/header-title/HeaderTitle.scss
@@ -2,3 +2,21 @@
font-size: var(--pgn-typography-font-size-sm);
font-weight: var(--pgn-typography-font-weight-normal);
}
+
+.unit-header-title {
+ .edit-button {
+ opacity: 0;
+ transition: opacity .3s linear;
+ margin-right: .5rem;
+
+ &:focus {
+ opacity: 1;
+ }
+ }
+
+ &:hover {
+ .edit-button {
+ opacity: 1;
+ }
+ }
+}
diff --git a/src/course-unit/header-title/HeaderTitle.test.jsx b/src/course-unit/header-title/HeaderTitle.test.jsx
deleted file mode 100644
index f48d919c1..000000000
--- a/src/course-unit/header-title/HeaderTitle.test.jsx
+++ /dev/null
@@ -1,170 +0,0 @@
-import MockAdapter from 'axios-mock-adapter';
-import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
-import { render, waitFor } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-import { IntlProvider } from '@edx/frontend-platform/i18n';
-import { AppProvider } from '@edx/frontend-platform/react';
-import { initializeMockApp } from '@edx/frontend-platform';
-
-import initializeStore from '../../store';
-import { executeThunk } from '../../utils';
-import { getCourseSectionVerticalApiUrl } from '../data/api';
-import { fetchCourseSectionVerticalData } from '../data/thunk';
-import { courseSectionVerticalMock } from '../__mocks__';
-import HeaderTitle from './HeaderTitle';
-import messages from './messages';
-
-const blockId = '123';
-const unitTitle = 'Getting Started';
-const isTitleEditFormOpen = false;
-const handleTitleEdit = jest.fn();
-const handleTitleEditSubmit = jest.fn();
-const handleConfigureSubmit = jest.fn();
-let store;
-let axiosMock;
-
-const renderComponent = (props) => render(
-
-
-
-
- ,
-);
-
-describe('', () => {
- beforeEach(async () => {
- initializeMockApp({
- authenticatedUser: {
- userId: 3,
- username: 'abc123',
- administrator: true,
- roles: [],
- },
- });
-
- store = initializeStore();
- axiosMock = new MockAdapter(getAuthenticatedHttpClient());
- axiosMock
- .onGet(getCourseSectionVerticalApiUrl(blockId))
- .reply(200, courseSectionVerticalMock);
- await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);
- });
-
- it('render HeaderTitle component correctly', () => {
- const { getByText, getByRole } = renderComponent();
-
- expect(getByText(unitTitle)).toBeInTheDocument();
- expect(getByRole('button', { name: messages.altButtonEdit.defaultMessage })).toBeInTheDocument();
- expect(getByRole('button', { name: messages.altButtonSettings.defaultMessage })).toBeInTheDocument();
- });
-
- it('render HeaderTitle with open edit form', () => {
- const { getByRole } = renderComponent({
- isTitleEditFormOpen: true,
- });
-
- expect(getByRole('textbox', { name: messages.ariaLabelButtonEdit.defaultMessage })).toBeInTheDocument();
- expect(getByRole('textbox', { name: messages.ariaLabelButtonEdit.defaultMessage })).toHaveValue(unitTitle);
- expect(getByRole('button', { name: messages.altButtonEdit.defaultMessage })).toBeEnabled();
- expect(getByRole('button', { name: messages.altButtonSettings.defaultMessage })).toBeEnabled();
- });
-
- it('Units sourced from upstream show a enabled edit button', async () => {
- // Override mock unit with one sourced from an upstream library
- axiosMock = new MockAdapter(getAuthenticatedHttpClient());
- axiosMock
- .onGet(getCourseSectionVerticalApiUrl(blockId))
- .reply(200, {
- ...courseSectionVerticalMock,
- xblock_info: {
- ...courseSectionVerticalMock.xblock_info,
- upstreamInfo: {
- ...courseSectionVerticalMock.xblock_info.upstreamInfo,
- upstreamRef: 'lct:org:lib:unit:unit-1',
- },
- },
- });
- await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);
-
- const { getByRole } = renderComponent();
-
- expect(getByRole('button', { name: messages.altButtonEdit.defaultMessage })).toBeEnabled();
- expect(getByRole('button', { name: messages.altButtonSettings.defaultMessage })).toBeEnabled();
- });
-
- it('calls toggle edit title form by clicking on Edit button', async () => {
- const user = userEvent.setup();
- const { getByRole } = renderComponent();
-
- const editTitleButton = getByRole('button', { name: messages.altButtonEdit.defaultMessage });
- await user.click(editTitleButton);
- expect(handleTitleEdit).toHaveBeenCalledTimes(1);
- });
-
- it('calls saving title by clicking outside or press Enter key', async () => {
- const user = userEvent.setup();
- const { getByRole } = renderComponent({
- isTitleEditFormOpen: true,
- });
-
- const titleField = getByRole('textbox', { name: messages.ariaLabelButtonEdit.defaultMessage });
- await user.type(titleField, ' 1');
- expect(titleField).toHaveValue(`${unitTitle} 1`);
- await user.click(document.body);
- expect(handleTitleEditSubmit).toHaveBeenCalledTimes(1);
-
- await user.click(titleField);
- await user.type(titleField, ' 2[Enter]');
- expect(titleField).toHaveValue(`${unitTitle} 1 2`);
- expect(handleTitleEditSubmit).toHaveBeenCalledTimes(2);
- });
-
- it('displays a visibility message with the selected groups for the unit', async () => {
- axiosMock
- .onGet(getCourseSectionVerticalApiUrl(blockId))
- .reply(200, {
- ...courseSectionVerticalMock,
- xblock_info: {
- ...courseSectionVerticalMock.xblock_info,
- user_partition_info: {
- ...courseSectionVerticalMock.xblock_info.user_partition_info,
- selected_partition_index: 1,
- selected_groups_label: 'Visibility group 1',
- },
- },
- });
- await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);
- const { getByText } = renderComponent();
- const visibilityMessage = messages.definedVisibilityMessage.defaultMessage
- .replace('{selectedGroupsLabel}', 'Visibility group 1');
-
- await waitFor(() => {
- expect(getByText(visibilityMessage)).toBeInTheDocument();
- });
- });
-
- it('displays a visibility message with the selected groups for some of xblock', async () => {
- axiosMock
- .onGet(getCourseSectionVerticalApiUrl(blockId))
- .reply(200, {
- ...courseSectionVerticalMock,
- xblock_info: {
- ...courseSectionVerticalMock.xblock_info,
- has_partition_group_components: true,
- },
- });
- await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);
- const { getByText } = renderComponent();
-
- await waitFor(() => {
- expect(getByText(messages.commonVisibilityMessage.defaultMessage)).toBeInTheDocument();
- });
- });
-});
diff --git a/src/course-unit/header-title/HeaderTitle.test.tsx b/src/course-unit/header-title/HeaderTitle.test.tsx
new file mode 100644
index 000000000..3ec4852f0
--- /dev/null
+++ b/src/course-unit/header-title/HeaderTitle.test.tsx
@@ -0,0 +1,113 @@
+import MockAdapter from 'axios-mock-adapter';
+import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
+import { initializeMocks, render, screen } from '@src/testUtils';
+import userEvent from '@testing-library/user-event';
+import { executeThunk } from '@src/utils';
+
+import { getCourseSectionVerticalApiUrl } from '../data/api';
+import { fetchCourseSectionVerticalData } from '../data/thunk';
+import { courseSectionVerticalMock } from '../__mocks__';
+import HeaderTitle from './HeaderTitle';
+import messages from './messages';
+
+const blockId = '123';
+const unitTitle = 'Getting Started';
+const isTitleEditFormOpen = false;
+const handleTitleEdit = jest.fn();
+const handleTitleEditSubmit = jest.fn();
+const handleConfigureSubmit = jest.fn();
+let store;
+let axiosMock;
+
+const renderComponent = (props?: any) => render(
+ ,
+);
+
+describe('', () => {
+ beforeEach(async () => {
+ const mocks = initializeMocks();
+
+ store = mocks.reduxStore;
+ axiosMock = mocks.axiosMock;
+ axiosMock
+ .onGet(getCourseSectionVerticalApiUrl(blockId))
+ .reply(200, courseSectionVerticalMock);
+ await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);
+ });
+
+ it('render HeaderTitle component correctly', () => {
+ renderComponent();
+
+ expect(screen.getByText(unitTitle)).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: messages.altButtonEdit.defaultMessage })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: messages.altButtonSettings.defaultMessage })).toBeInTheDocument();
+ });
+
+ it('render HeaderTitle with open edit form', () => {
+ renderComponent({
+ isTitleEditFormOpen: true,
+ });
+
+ expect(screen.getByRole('textbox', { name: messages.ariaLabelButtonEdit.defaultMessage })).toBeInTheDocument();
+ expect(screen.getByRole('textbox', { name: messages.ariaLabelButtonEdit.defaultMessage })).toHaveValue(unitTitle);
+ expect(screen.getByRole('button', { name: messages.altButtonEdit.defaultMessage })).toBeEnabled();
+ expect(screen.getByRole('button', { name: messages.altButtonSettings.defaultMessage })).toBeEnabled();
+ });
+
+ it('Units sourced from upstream show a enabled edit button', async () => {
+ // Override mock unit with one sourced from an upstream library
+ axiosMock = new MockAdapter(getAuthenticatedHttpClient());
+ axiosMock
+ .onGet(getCourseSectionVerticalApiUrl(blockId))
+ .reply(200, {
+ ...courseSectionVerticalMock,
+ xblock_info: {
+ ...courseSectionVerticalMock.xblock_info,
+ upstreamInfo: {
+ ...courseSectionVerticalMock.xblock_info.upstreamInfo,
+ upstreamRef: 'lct:org:lib:unit:unit-1',
+ },
+ },
+ });
+ await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);
+
+ renderComponent();
+
+ expect(screen.getByRole('button', { name: messages.altButtonEdit.defaultMessage })).toBeEnabled();
+ expect(screen.getByRole('button', { name: messages.altButtonSettings.defaultMessage })).toBeEnabled();
+ });
+
+ it('calls toggle edit title form by clicking on Edit button', async () => {
+ const user = userEvent.setup();
+ renderComponent();
+
+ const editTitleButton = screen.getByRole('button', { name: messages.altButtonEdit.defaultMessage });
+ await user.click(editTitleButton);
+ expect(handleTitleEdit).toHaveBeenCalledTimes(1);
+ });
+
+ it('calls saving title by clicking outside or press Enter key', async () => {
+ const user = userEvent.setup();
+ renderComponent({
+ isTitleEditFormOpen: true,
+ });
+
+ const titleField = screen.getByRole('textbox', { name: messages.ariaLabelButtonEdit.defaultMessage });
+ await user.type(titleField, ' 1');
+ expect(titleField).toHaveValue(`${unitTitle} 1`);
+ await user.click(document.body);
+ expect(handleTitleEditSubmit).toHaveBeenCalledTimes(1);
+
+ await user.click(titleField);
+ await user.type(titleField, ' 2[Enter]');
+ expect(titleField).toHaveValue(`${unitTitle} 1 2`);
+ expect(handleTitleEditSubmit).toHaveBeenCalledTimes(2);
+ });
+});
diff --git a/src/course-unit/header-title/HeaderTitle.tsx b/src/course-unit/header-title/HeaderTitle.tsx
new file mode 100644
index 000000000..623c099e9
--- /dev/null
+++ b/src/course-unit/header-title/HeaderTitle.tsx
@@ -0,0 +1,121 @@
+import { useEffect, useState } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import {
+ Form, IconButton, useToggle,
+} from '@openedx/paragon';
+import {
+ EditOutline as EditIcon,
+ Settings as SettingsIcon,
+} from '@openedx/paragon/icons';
+
+import ConfigureModal from '@src/generic/configure-modal/ConfigureModal';
+import { COURSE_BLOCK_NAMES } from '@src/constants';
+import { useIntl } from '@edx/frontend-platform/i18n';
+import { getCourseUnitData } from '../data/selectors';
+import { updateQueryPendingStatus } from '../data/slice';
+import messages from './messages';
+import { isUnitPageNewDesignEnabled } from '../utils';
+
+type HeaderTitleProps = {
+ unitTitle: string;
+ isTitleEditFormOpen: boolean;
+ handleTitleEdit: () => void;
+ handleTitleEditSubmit: (title: string) => void;
+ handleConfigureSubmit: (
+ id: string,
+ isVisible: boolean,
+ groupAccess: boolean,
+ isDiscussionEnabled: boolean,
+ closeModalFn: (value: boolean) => void
+ ) => void;
+};
+
+/**
+ * Component that renders the title and extra action buttons:
+ * - Edit button: Hidden, It appears when you hover over it.
+ * The title becomes a text form.
+ * - Settings button: Shown only in the legacy unit page.
+ * Opens a settings modal.
+ */
+const HeaderTitle = ({
+ unitTitle,
+ isTitleEditFormOpen,
+ handleTitleEdit,
+ handleTitleEditSubmit,
+ handleConfigureSubmit,
+}: HeaderTitleProps) => {
+ const intl = useIntl();
+ const dispatch = useDispatch();
+ const [titleValue, setTitleValue] = useState(unitTitle);
+ const currentItemData = useSelector(getCourseUnitData);
+ const [isConfigureModalOpen, openConfigureModal, closeConfigureModal] = useToggle(false);
+
+ const isXBlockComponent = [
+ COURSE_BLOCK_NAMES.libraryContent.id,
+ COURSE_BLOCK_NAMES.splitTest.id,
+ COURSE_BLOCK_NAMES.component.id,
+ ].includes(currentItemData.category);
+
+ const onConfigureSubmit = (...arg) => {
+ handleConfigureSubmit(
+ currentItemData.id,
+ arg[0],
+ arg[1],
+ arg[2],
+ closeConfigureModal,
+ );
+ };
+
+ useEffect(() => {
+ setTitleValue(unitTitle);
+ dispatch(updateQueryPendingStatus(true));
+ }, [unitTitle]);
+
+ return (
+
+ {isTitleEditFormOpen ? (
+
+ e && e.focus()}
+ value={titleValue}
+ name="displayName"
+ onChange={(e) => setTitleValue(e.target.value)}
+ aria-label={intl.formatMessage(messages.ariaLabelButtonEdit)}
+ onBlur={() => handleTitleEditSubmit(titleValue)}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ handleTitleEditSubmit(titleValue);
+ }
+ }}
+ />
+
+ ) : unitTitle}
+
+ {!isUnitPageNewDesignEnabled() && (
+ <>
+
+
+ >
+ )}
+
+ );
+};
+
+export default HeaderTitle;
diff --git a/src/course-unit/header-title/messages.ts b/src/course-unit/header-title/messages.ts
index 036e9ddef..334436f17 100644
--- a/src/course-unit/header-title/messages.ts
+++ b/src/course-unit/header-title/messages.ts
@@ -21,11 +21,6 @@ const messages = defineMessages({
defaultMessage: 'Access to this unit is restricted to: {selectedGroupsLabel}',
description: 'Group visibility accessibility text for Unit',
},
- commonVisibilityMessage: {
- id: 'course-authoring.course-unit.heading.visibility.common.message',
- defaultMessage: 'Access to some content in this unit is restricted to specific groups of learners.',
- description: 'The label text of some content restriction in this unit',
- },
});
export default messages;
diff --git a/src/course-unit/hooks.jsx b/src/course-unit/hooks.jsx
index b987599ee..14d7403e4 100644
--- a/src/course-unit/hooks.jsx
+++ b/src/course-unit/hooks.jsx
@@ -49,7 +49,7 @@ export const useCourseUnit = ({ courseId, blockId }) => {
const dispatch = useDispatch();
const [searchParams] = useSearchParams();
const { sendMessageToIframe } = useIframe();
- const [addComponentTemplateData, setAddComponentTemplateData] = useState({});
+ const [addComponentTemplateData, setAddComponentTemplateData] = useState(undefined);
const [isMoveModalOpen, openMoveModal, closeMoveModal] = useToggle(false);
const courseUnit = useSelector(getCourseUnitData);
diff --git a/src/course-unit/messages.ts b/src/course-unit/messages.ts
index bbda3588e..334becd2e 100644
--- a/src/course-unit/messages.ts
+++ b/src/course-unit/messages.ts
@@ -53,6 +53,46 @@ const messages = defineMessages({
defaultMessage: 'library',
description: 'Text of the link in the alert when the unit is read only because is a library unit',
},
+ statusBarDraftChangesBadge: {
+ id: 'course-authoring.course-unit.status-bar.publish-status.draft-changes',
+ defaultMessage: 'Unpublished changes',
+ description: 'Text for the Draft Changes Badge in the status bar.',
+ },
+ statusBarDiscussionsEnabled: {
+ id: 'course-authoring.course-unit.status-bar.discussions-enabled',
+ defaultMessage: 'Discussions Enabled',
+ description: 'Text for the Discussions enabled Badge in the status bar.',
+ },
+ statusBarDraftNeverPublished: {
+ id: 'course-authoring.course-unit.status-bar.visibility.draft',
+ defaultMessage: 'Draft (Never Published)',
+ description: 'Text for the Discussions enabled Badge in the status bar.',
+ },
+ statusBarGroupAccessOneGroup: {
+ id: 'course-authoring.course-unit.status-bar.access.one-group',
+ defaultMessage: 'Access: {groupName}',
+ description: 'Text in the status bar when the access for the unit is for one group',
+ },
+ statusBarGroupAccessMultipleGroup: {
+ id: 'course-authoring.course-unit.status-bar.access.multiple-group',
+ defaultMessage: 'Access: {groupsCount} Groups',
+ description: 'Text in the status bar when the access for the unit is for one group',
+ },
+ statusBarLiveBadge: {
+ id: 'course-authoring.course-unit.status-bar.visibility.chip',
+ defaultMessage: 'Live',
+ description: 'Text for the Live Badge in the status bar.',
+ },
+ statusBarStaffOnly: {
+ id: 'course-authoring.course-unit.status-bar.visibility.staff-only',
+ defaultMessage: 'Staff Only',
+ description: 'Text for the Staff Only Badge in the status bar.',
+ },
+ statusBarScheduledBadge: {
+ id: 'course-authoring.course-unit.status-bar.visibility.scheduled',
+ defaultMessage: 'Scheduled',
+ description: 'Text for the Upcoming Badge in the status bar.',
+ },
});
export default messages;
diff --git a/src/course-unit/utils.ts b/src/course-unit/utils.ts
index c912c94d7..26532036a 100644
--- a/src/course-unit/utils.ts
+++ b/src/course-unit/utils.ts
@@ -1,3 +1,5 @@
+import { getConfig } from '@edx/frontend-platform';
+
/**
* Adapts API URL paths to the application's internal URL format based on predefined conditions.
*
@@ -43,3 +45,7 @@ export const subsectionFirstUnitEditUrl = (
const url = `/course/${courseId}/subsection/${subsectionId}`;
return url;
};
+
+export const isUnitPageNewDesignEnabled = () => (
+ getConfig().ENABLE_UNIT_PAGE_NEW_DESIGN?.toString().toLowerCase() === 'true'
+);
diff --git a/src/generic/alert-message/index.tsx b/src/generic/alert-message/index.tsx
index 244778ecf..16f84e93a 100644
--- a/src/generic/alert-message/index.tsx
+++ b/src/generic/alert-message/index.tsx
@@ -1,8 +1,8 @@
import React from 'react';
import { Alert } from '@openedx/paragon';
-interface Props extends React.ComponentPropsWithoutRef {
- title?: string;
+interface Props extends Omit, 'title'> {
+ title?: string | React.ReactNode;
description?: string | React.ReactNode;
}
diff --git a/src/index.jsx b/src/index.jsx
index 516695cc6..671d627f5 100755
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -174,6 +174,7 @@ initialize({
ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN: process.env.ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN || 'false',
ENABLE_CERTIFICATE_PAGE: process.env.ENABLE_CERTIFICATE_PAGE || 'false',
ENABLE_COURSE_IMPORT_IN_LIBRARY: process.env.ENABLE_COURSE_IMPORT_IN_LIBRARY || 'false',
+ ENABLE_UNIT_PAGE_NEW_DESIGN: process.env.ENABLE_UNIT_PAGE_NEW_DESIGN || 'false',
ENABLE_COURSE_OUTLINE_NEW_DESIGN: process.env.ENABLE_COURSE_OUTLINE_NEW_DESIGN || 'false',
ENABLE_TAGGING_TAXONOMY_PAGES: process.env.ENABLE_TAGGING_TAXONOMY_PAGES || 'false',
ENABLE_CHECKLIST_QUALITY: process.env.ENABLE_CHECKLIST_QUALITY || 'true',