-
+
+
+ {intl.formatMessage(messages.noCourseUpdates)}
+
+
+
+
+ )}
handleOpenUpdateForm(REQUEST_TYPES.edit_handouts)}
- isDisabledButtons={isUpdateFormOpen}
+ isDisabledButtons={isUpdateFormOpen || errors.loadingHandouts}
/>
({
const RootWrapper = () => (
-
+
);
describe(' ', () => {
- beforeEach(() => {
- initializeMockApp({
- authenticatedUser: {
- userId: 3,
- username: 'abc123',
- administrator: true,
- roles: [],
- },
+ describe('Successful API responses', () => {
+ beforeEach(() => {
+ initializeMockApp({
+ authenticatedUser: {
+ userId: 3,
+ username: 'abc123',
+ administrator: true,
+ roles: [],
+ },
+ });
+
+ store = initializeStore();
+ axiosMock = new MockAdapter(getAuthenticatedHttpClient());
+ axiosMock
+ .onGet(getCourseUpdatesApiUrl(courseId))
+ .reply(200, courseUpdatesMock);
+ axiosMock
+ .onGet(getCourseHandoutApiUrl(courseId))
+ .reply(200, courseHandoutsMock);
});
- store = initializeStore();
- axiosMock = new MockAdapter(getAuthenticatedHttpClient());
- axiosMock
- .onGet(getCourseUpdatesApiUrl(courseId))
- .reply(200, courseUpdatesMock);
- axiosMock
- .onGet(getCourseHandoutApiUrl(courseId))
- .reply(200, courseHandoutsMock);
- });
+ it('render CourseUpdates component correctly', async () => {
+ const {
+ getByText, getAllByTestId, getByTestId, getByRole,
+ } = render( );
- it('render CourseUpdates component correctly', async () => {
- const {
- getByText, getAllByTestId, getByTestId, getByRole,
- } = render( );
-
- await waitFor(() => {
- expect(getByText(messages.headingTitle.defaultMessage)).toBeInTheDocument();
- expect(getByText(messages.headingSubtitle.defaultMessage)).toBeInTheDocument();
- expect(getByText(messages.sectionInfo.defaultMessage)).toBeInTheDocument();
- expect(getByRole('button', { name: messages.newUpdateButton.defaultMessage })).toBeInTheDocument();
- expect(getAllByTestId('course-update')).toHaveLength(3);
- expect(getByTestId('course-handouts')).toBeInTheDocument();
+ await waitFor(() => {
+ expect(getByText(messages.headingTitle.defaultMessage)).toBeInTheDocument();
+ expect(getByText(messages.headingSubtitle.defaultMessage)).toBeInTheDocument();
+ expect(getByText(messages.sectionInfo.defaultMessage)).toBeInTheDocument();
+ expect(getByRole('button', { name: messages.newUpdateButton.defaultMessage })).toBeInTheDocument();
+ expect(getAllByTestId('course-update')).toHaveLength(3);
+ expect(getByTestId('course-handouts')).toBeInTheDocument();
+ });
});
- });
- it('should create course update', async () => {
- const { getByText } = render( );
+ it('should create course update', async () => {
+ const { getByText } = render( );
- const data = {
- content: ' );
-
- const data = {
- id: courseUpdatesMock[0].id,
- content: ' );
-
- axiosMock
- .onDelete(updateCourseUpdatesApiUrl(courseId, courseUpdatesMock[0].id))
- .reply(200);
-
- await executeThunk(deleteCourseUpdateQuery(courseId, courseUpdatesMock[0].id), store.dispatch);
- expect(queryByText(courseUpdatesMock[0].date)).not.toBeInTheDocument();
- expect(queryByText(courseUpdatesMock[0].content)).not.toBeInTheDocument();
- });
-
- it('should edit course handouts', async () => {
- const { getByText, queryByText } = render( );
-
- const data = {
- ...courseHandoutsMock,
- data: ' );
-
- await waitFor(() => {
- const editUpdateButtons = getAllByTestId('course-update-edit-button');
- const deleteButtons = getAllByTestId('course-update-delete-button');
- const editHandoutsButtons = getAllByTestId('course-handouts-edit-button');
- const newUpdateButton = getByRole('button', { name: messages.newUpdateButton.defaultMessage });
-
- fireEvent.click(newUpdateButton);
-
- expect(newUpdateButton).toBeDisabled();
- editUpdateButtons.forEach((button) => expect(button).toBeDisabled());
- editHandoutsButtons.forEach((button) => expect(button).toBeDisabled());
- deleteButtons.forEach((button) => expect(button).toBeDisabled());
- expect(getByText('Add new update')).toBeInTheDocument();
+ await executeThunk(createCourseUpdateQuery(courseId, data), store.dispatch);
+ expect(getByText('Some text')).toBeInTheDocument();
+ expect(getByText(data.date)).toBeInTheDocument();
});
- });
- it('Edit handouts form is visible after clicking "Edit" button', async () => {
- const { getByText, getByRole, getAllByTestId } = render( );
+ it('should edit course update', async () => {
+ const { getByText, queryByText } = render( );
- await waitFor(() => {
- const editUpdateButtons = getAllByTestId('course-update-edit-button');
- const deleteButtons = getAllByTestId('course-update-delete-button');
- const editHandoutsButtons = getAllByTestId('course-handouts-edit-button');
- const editHandoutsButton = editHandoutsButtons[0];
+ const data = {
+ id: courseUpdatesMock[0].id,
+ content: ' );
-
- await waitFor(() => {
- const editUpdateButtons = getAllByTestId('course-update-edit-button');
- const deleteButtons = getAllByTestId('course-update-delete-button');
- const editHandoutsButtons = getAllByTestId('course-handouts-edit-button');
- const editUpdateFirstButton = editUpdateButtons[0];
-
- fireEvent.click(editUpdateFirstButton);
- expect(getByText('Edit update')).toBeInTheDocument();
- expect(getByRole('button', { name: messages.newUpdateButton.defaultMessage })).toBeDisabled();
- editUpdateButtons.forEach((button) => expect(button).toBeDisabled());
- editHandoutsButtons.forEach((button) => expect(button).toBeDisabled());
- deleteButtons.forEach((button) => expect(button).toBeDisabled());
+ await executeThunk(editCourseUpdateQuery(courseId, data), store.dispatch);
+ expect(getByText('Some text')).toBeInTheDocument();
+ expect(getByText(data.date)).toBeInTheDocument();
+ expect(queryByText(courseUpdatesMock[0].date)).not.toBeInTheDocument();
expect(queryByText(courseUpdatesMock[0].content)).not.toBeInTheDocument();
});
+
+ it('should delete course update', async () => {
+ const { queryByText } = render( );
+
+ axiosMock
+ .onDelete(updateCourseUpdatesApiUrl(courseId, courseUpdatesMock[0].id))
+ .reply(200);
+
+ await executeThunk(deleteCourseUpdateQuery(courseId, courseUpdatesMock[0].id), store.dispatch);
+ expect(queryByText(courseUpdatesMock[0].date)).not.toBeInTheDocument();
+ expect(queryByText(courseUpdatesMock[0].content)).not.toBeInTheDocument();
+ });
+
+ it('should edit course handouts', async () => {
+ const { getByText, queryByText } = render( );
+
+ const data = {
+ ...courseHandoutsMock,
+ data: ' );
+
+ await waitFor(() => {
+ const editUpdateButtons = getAllByTestId('course-update-edit-button');
+ const deleteButtons = getAllByTestId('course-update-delete-button');
+ const editHandoutsButtons = getAllByTestId('course-handouts-edit-button');
+ const newUpdateButton = getByRole('button', { name: messages.newUpdateButton.defaultMessage });
+
+ fireEvent.click(newUpdateButton);
+
+ expect(newUpdateButton).toBeDisabled();
+ editUpdateButtons.forEach((button) => expect(button).toBeDisabled());
+ editHandoutsButtons.forEach((button) => expect(button).toBeDisabled());
+ deleteButtons.forEach((button) => expect(button).toBeDisabled());
+ expect(getByText('Add new update')).toBeInTheDocument();
+ });
+ });
+
+ it('Edit handouts form is visible after clicking "Edit" button', async () => {
+ const { getByText, getByRole, getAllByTestId } = render( );
+
+ await waitFor(() => {
+ const editUpdateButtons = getAllByTestId('course-update-edit-button');
+ const deleteButtons = getAllByTestId('course-update-delete-button');
+ const editHandoutsButtons = getAllByTestId('course-handouts-edit-button');
+ const editHandoutsButton = editHandoutsButtons[0];
+
+ fireEvent.click(editHandoutsButton);
+
+ expect(editHandoutsButton).toBeDisabled();
+ expect(getByRole('button', { name: messages.newUpdateButton.defaultMessage })).toBeDisabled();
+ editUpdateButtons.forEach((button) => expect(button).toBeDisabled());
+ editHandoutsButtons.forEach((button) => expect(button).toBeDisabled());
+ deleteButtons.forEach((button) => expect(button).toBeDisabled());
+ expect(getByText('Edit handouts')).toBeInTheDocument();
+ });
+ });
+
+ it('Edit update form is visible after clicking "Edit" button', async () => {
+ const {
+ getByText, getByRole, getAllByTestId, queryByText,
+ } = render( );
+
+ await waitFor(() => {
+ const editUpdateButtons = getAllByTestId('course-update-edit-button');
+ const deleteButtons = getAllByTestId('course-update-delete-button');
+ const editHandoutsButtons = getAllByTestId('course-handouts-edit-button');
+ const editUpdateFirstButton = editUpdateButtons[0];
+
+ fireEvent.click(editUpdateFirstButton);
+ expect(getByText('Edit update')).toBeInTheDocument();
+ expect(getByRole('button', { name: messages.newUpdateButton.defaultMessage })).toBeDisabled();
+ editUpdateButtons.forEach((button) => expect(button).toBeDisabled());
+ editHandoutsButtons.forEach((button) => expect(button).toBeDisabled());
+ deleteButtons.forEach((button) => expect(button).toBeDisabled());
+ expect(queryByText(courseUpdatesMock[0].content)).not.toBeInTheDocument();
+ });
+ });
+ });
+
+ describe('page load failure API responses', () => {
+ beforeEach(() => {
+ initializeMockApp({
+ authenticatedUser: {
+ userId: 3,
+ username: 'abc123',
+ administrator: true,
+ roles: [],
+ },
+ });
+
+ store = initializeStore();
+ axiosMock = new MockAdapter(getAuthenticatedHttpClient());
+ });
+
+ it('Course updates fetch should show updates loading error', async () => {
+ axiosMock
+ .onGet(getCourseUpdatesApiUrl(courseId))
+ .reply(404);
+ axiosMock
+ .onGet(getCourseHandoutApiUrl(courseId))
+ .reply(200, courseHandoutsMock);
+
+ const {
+ getByText, queryByTestId, getByRole,
+ } = render( );
+
+ await waitFor(() => {
+ const newButton = getByRole('button', { name: messages.newUpdateButton.defaultMessage });
+ expect(getByText(messages.loadingUpdatesErrorTitle.defaultMessage));
+ expect(newButton).toBeDisabled();
+ expect(getByText(messages.noCourseUpdates.defaultMessage)).toBeVisible();
+ expect(queryByTestId('course-update')).toBeNull();
+ });
+ });
+
+ it('Course handouts fetch should show handouts loading error', async () => {
+ axiosMock
+ .onGet(getCourseUpdatesApiUrl(courseId))
+ .reply(200, courseUpdatesMock);
+ axiosMock
+ .onGet(getCourseHandoutApiUrl(courseId))
+ .reply(404);
+
+ const {
+ getByText, getByTestId,
+ } = render( );
+
+ await waitFor(() => {
+ expect(getByText(messages.loadingHandoutsErrorTitle.defaultMessage));
+ expect(getByTestId('course-handouts-edit-button')).toBeDisabled();
+ });
+ });
+ });
+
+ describe('saving failure API responses', () => {
+ beforeEach(() => {
+ initializeMockApp({
+ authenticatedUser: {
+ userId: 3,
+ username: 'abc123',
+ administrator: true,
+ roles: [],
+ },
+ });
+
+ store = initializeStore();
+ axiosMock = new MockAdapter(getAuthenticatedHttpClient());
+ axiosMock
+ .onGet(getCourseUpdatesApiUrl(courseId))
+ .reply(200, courseUpdatesMock);
+ axiosMock
+ .onGet(getCourseHandoutApiUrl(courseId))
+ .reply(200, courseHandoutsMock);
+ });
+ it('creating new update should show saving error alert', async () => {
+ const { getByText, queryByText } = render( );
+
+ const data = {
+ content: ' );
+
+ const data = {
+ id: courseUpdatesMock[0].id,
+ content: ' );
+
+ axiosMock
+ .onDelete(updateCourseUpdatesApiUrl(courseId, courseUpdatesMock[0].id))
+ .reply(404);
+
+ await executeThunk(deleteCourseUpdateQuery(courseId, courseUpdatesMock[0].id), store.dispatch);
+ expect(getByText(courseUpdatesMock[0].date)).toBeVisible();
+ expect(getByText(courseUpdatesMock[0].content)).toBeVisible();
+ expect(getByText(messages.deletingUpdatesErrorDescription.defaultMessage)).toBeVisible();
+ });
+
+ it('editing course handouts should show saving error alert', async () => {
+ const { getByText, queryByText } = render( );
+
+ const data = {
+ ...courseHandoutsMock,
+ data: '
- {courseUpdates.length ? courseUpdates.map((courseUpdate, index) => (
- isInnerFormOpen(courseUpdate.id) ? (
-
- ) : (
- handleOpenUpdateForm(REQUEST_TYPES.edit_update, courseUpdate)}
- onDelete={() => handleOpenDeleteForm(courseUpdate)}
- isDisabledButtons={isUpdateFormOpen}
- />
- ))) : null}
-
+ {courseUpdates.length > 0 && (
+
+ {courseUpdates.map((courseUpdate, index) => (
+ isInnerFormOpen(courseUpdate.id) ? (
+
+ ) : (
+ handleOpenUpdateForm(REQUEST_TYPES.edit_update, courseUpdate)}
+ onDelete={() => handleOpenDeleteForm(courseUpdate)}
+ isDisabledButtons={isUpdateFormOpen}
+ />
+ )
+ ))}
+
+ )}
+ {!courseUpdates.length && (
+ Some text
', - date: 'August 29, 2023', - }; + const data = { + content: 'Some text
', + date: 'August 29, 2023', + }; - axiosMock - .onPost(getCourseUpdatesApiUrl(courseId)) - .reply(200, data); + axiosMock + .onPost(getCourseUpdatesApiUrl(courseId)) + .reply(200, data); - await executeThunk(createCourseUpdateQuery(courseId, data), store.dispatch); - expect(getByText('Some text')).toBeInTheDocument(); - expect(getByText(data.date)).toBeInTheDocument(); - }); - - it('should edit course update', async () => { - const { getByText, queryByText } = render(Some text
', - date: 'August 29, 2023', - }; - - axiosMock - .onPut(updateCourseUpdatesApiUrl(courseId, courseUpdatesMock[0].id)) - .reply(200, data); - - await executeThunk(editCourseUpdateQuery(courseId, data), store.dispatch); - expect(getByText('Some text')).toBeInTheDocument(); - expect(getByText(data.date)).toBeInTheDocument(); - expect(queryByText(courseUpdatesMock[0].date)).not.toBeInTheDocument(); - expect(queryByText(courseUpdatesMock[0].content)).not.toBeInTheDocument(); - }); - - it('should delete course update', async () => { - const { queryByText } = render(Some handouts 1
', - }; - - axiosMock - .onPut(getCourseHandoutApiUrl(courseId)) - .reply(200, data); - - await executeThunk(editCourseHandoutsQuery(courseId, data), store.dispatch); - expect(getByText('Some handouts 1')).toBeInTheDocument(); - expect(queryByText(courseHandoutsMock.data)).not.toBeInTheDocument(); - }); - - it('Add new update form is visible after clicking "New update" button', async () => { - const { getByText, getByRole, getAllByTestId } = render(Some text
', + date: 'August 29, 2023', + }; - fireEvent.click(editHandoutsButton); + axiosMock + .onPut(updateCourseUpdatesApiUrl(courseId, courseUpdatesMock[0].id)) + .reply(200, data); - expect(editHandoutsButton).toBeDisabled(); - expect(getByRole('button', { name: messages.newUpdateButton.defaultMessage })).toBeDisabled(); - editUpdateButtons.forEach((button) => expect(button).toBeDisabled()); - editHandoutsButtons.forEach((button) => expect(button).toBeDisabled()); - deleteButtons.forEach((button) => expect(button).toBeDisabled()); - expect(getByText('Edit handouts')).toBeInTheDocument(); - }); - }); - - it('Edit update form is visible after clicking "Edit" button', async () => { - const { - getByText, getByRole, getAllByTestId, queryByText, - } = render(Some handouts 1
', + }; + + axiosMock + .onPut(getCourseHandoutApiUrl(courseId)) + .reply(200, data); + + await executeThunk(editCourseHandoutsQuery(courseId, data), store.dispatch); + expect(getByText('Some handouts 1')).toBeInTheDocument(); + expect(queryByText(courseHandoutsMock.data)).not.toBeInTheDocument(); + }); + + it('Add new update form is visible after clicking "New update" button', async () => { + const { getByText, getByRole, getAllByTestId } = render(Some text
', + date: 'August 29, 2023', + }; + + axiosMock + .onPost(getCourseUpdatesApiUrl(courseId), data) + .reply(404); + + await executeThunk(createCourseUpdateQuery(courseId, data), store.dispatch); + expect(getByText(messages.savingNewUpdateErrorAlertDescription.defaultMessage)).toBeVisible(); + expect(queryByText('Some text')).toBeNull(); + expect(queryByText(data.date)).toBeNull(); + }); + + it('editing course update should show saving error alert', async () => { + const { getByText, queryByText } = render(Some text
', + date: 'August 29, 2023', + }; + + axiosMock + .onPut(updateCourseUpdatesApiUrl(courseId, courseUpdatesMock[0].id)) + .reply(404); + + await executeThunk(editCourseUpdateQuery(courseId, data), store.dispatch); + expect(queryByText('Some text')).toBeNull(); + expect(queryByText(data.date)).toBeNull(); + expect(getByText(courseUpdatesMock[0].date)).toBeVisible(); + expect(getByText(courseUpdatesMock[0].content)).toBeVisible(); + expect(getByText(messages.savingUpdatesErrorDescription.defaultMessage)).toBeVisible(); + }); + + it('deleting course update should show delete saving error alert', async () => { + const { getByText } = render(Some handouts 1
', + }; + + axiosMock + .onPut(getCourseHandoutApiUrl(courseId)) + .reply(404); + + await executeThunk(editCourseHandoutsQuery(courseId, data), store.dispatch); + expect(queryByText('Some handouts 1')).toBeNull(); + expect(getByText(courseHandoutsMock.data)).toBeVisible(); + expect(getByText(messages.savingHandoutsErrorDescription.defaultMessage)); + }); }); }); diff --git a/src/course-updates/data/selectors.js b/src/course-updates/data/selectors.js index 947ad0f8a..b062f6748 100644 --- a/src/course-updates/data/selectors.js +++ b/src/course-updates/data/selectors.js @@ -2,3 +2,4 @@ export const getCourseUpdates = (state) => state.courseUpdates.courseUpdates; export const getCourseHandouts = (state) => state.courseUpdates.courseHandouts; export const getSavingStatuses = (state) => state.courseUpdates.savingStatuses; export const getLoadingStatuses = (state) => state.courseUpdates.loadingStatuses; +export const getErrors = (state) => state.courseUpdates.errors; diff --git a/src/course-updates/data/slice.js b/src/course-updates/data/slice.js index 18cd86a1a..dbb130fcf 100644 --- a/src/course-updates/data/slice.js +++ b/src/course-updates/data/slice.js @@ -15,6 +15,14 @@ const initialState = { fetchCourseUpdatesQuery: '', fetchCourseHandoutsQuery: '', }, + errors: { + creatingUpdate: false, + deletingUpdates: false, + loadingUpdates: false, + loadingHandouts: false, + savingUpdates: false, + savingHandouts: false, + }, }; const slice = createSlice({ @@ -48,10 +56,14 @@ const slice = createSlice({ }; }, updateSavingStatuses: (state, { payload }) => { - state.savingStatuses = { ...state.savingStatuses, ...payload }; + const { status, error } = payload; + state.errors = { ...initialState.errors, ...error }; + state.savingStatuses = { ...state.savingStatuses, ...status }; }, updateLoadingStatuses: (state, { payload }) => { - state.loadingStatuses = { ...state.loadingStatuses, ...payload }; + const { status, error } = payload; + state.errors = { ...initialState.errors, ...error }; + state.loadingStatuses = { ...state.loadingStatuses, ...status }; }, }, }); diff --git a/src/course-updates/data/thunk.js b/src/course-updates/data/thunk.js index 8713b808c..88b3a0578 100644 --- a/src/course-updates/data/thunk.js +++ b/src/course-updates/data/thunk.js @@ -23,12 +23,18 @@ import { export function fetchCourseUpdatesQuery(courseId) { return async (dispatch) => { try { - dispatch(updateLoadingStatuses({ fetchCourseHandoutsQuery: RequestStatus.IN_PROGRESS })); + dispatch(updateLoadingStatuses({ fetchCourseUpdatesQuery: RequestStatus.IN_PROGRESS })); const courseUpdates = await getCourseUpdates(courseId); dispatch(fetchCourseUpdatesSuccess(courseUpdates)); - dispatch(updateLoadingStatuses({ fetchCourseHandoutsQuery: RequestStatus.SUCCESSFUL })); + dispatch(updateLoadingStatuses({ + status: { fetchCourseUpdatesQuery: RequestStatus.SUCCESSFUL }, + error: { loadingUpdates: false }, + })); } catch (error) { - dispatch(updateLoadingStatuses({ fetchCourseHandoutsQuery: RequestStatus.FAILED })); + dispatch(updateLoadingStatuses({ + status: { fetchCourseUpdatesQuery: RequestStatus.FAILED }, + error: { loadingUpdates: true }, + })); } }; } @@ -41,10 +47,16 @@ export function createCourseUpdateQuery(courseId, data) { const courseUpdate = await createUpdate(courseId, data); dispatch(createCourseUpdate(courseUpdate)); dispatch(hideProcessingNotification()); - dispatch(updateSavingStatuses({ createCourseUpdateQuery: RequestStatus.SUCCESSFUL })); + dispatch(updateSavingStatuses({ + status: { createCourseUpdateQuery: RequestStatus.SUCCESSFUL }, + error: { creatingUpdate: false }, + })); } catch (error) { dispatch(hideProcessingNotification()); - dispatch(updateSavingStatuses({ createCourseUpdateQuery: RequestStatus.FAILED })); + dispatch(updateSavingStatuses({ + status: { createCourseUpdateQuery: RequestStatus.FAILED }, + error: { creatingUpdate: true }, + })); } }; } @@ -57,10 +69,16 @@ export function editCourseUpdateQuery(courseId, data) { const courseUpdate = await editUpdate(courseId, data); dispatch(editCourseUpdate(courseUpdate)); dispatch(hideProcessingNotification()); - dispatch(updateSavingStatuses({ createCourseUpdateQuery: RequestStatus.SUCCESSFUL })); + dispatch(updateSavingStatuses({ + status: { createCourseUpdateQuery: RequestStatus.SUCCESSFUL }, + error: { savingUpdates: false }, + })); } catch (error) { dispatch(hideProcessingNotification()); - dispatch(updateSavingStatuses({ createCourseUpdateQuery: RequestStatus.FAILED })); + dispatch(updateSavingStatuses({ + status: { createCourseUpdateQuery: RequestStatus.FAILED }, + error: { savingUpdates: true }, + })); } }; } @@ -73,10 +91,16 @@ export function deleteCourseUpdateQuery(courseId, updateId) { const courseUpdates = await deleteUpdate(courseId, updateId); dispatch(deleteCourseUpdate(courseUpdates)); dispatch(hideProcessingNotification()); - dispatch(updateSavingStatuses({ createCourseUpdateQuery: RequestStatus.SUCCESSFUL })); + dispatch(updateSavingStatuses({ + status: { createCourseUpdateQuery: RequestStatus.SUCCESSFUL }, + error: { deletingUpdates: false }, + })); } catch (error) { dispatch(hideProcessingNotification()); - dispatch(updateSavingStatuses({ createCourseUpdateQuery: RequestStatus.FAILED })); + dispatch(updateSavingStatuses({ + status: { createCourseUpdateQuery: RequestStatus.FAILED }, + error: { deletingUpdates: true }, + })); } }; } @@ -87,9 +111,15 @@ export function fetchCourseHandoutsQuery(courseId) { dispatch(updateLoadingStatuses({ fetchCourseHandoutsQuery: RequestStatus.IN_PROGRESS })); const courseHandouts = await getCourseHandouts(courseId); dispatch(fetchCourseHandoutsSuccess(courseHandouts)); - dispatch(updateLoadingStatuses({ fetchCourseHandoutsQuery: RequestStatus.SUCCESSFUL })); + dispatch(updateLoadingStatuses({ + status: { fetchCourseHandoutsQuery: RequestStatus.SUCCESSFUL }, + error: { loadingHandouts: false }, + })); } catch (error) { - dispatch(updateLoadingStatuses({ fetchCourseHandoutsQuery: RequestStatus.FAILED })); + dispatch(updateLoadingStatuses({ + status: { fetchCourseHandoutsQuery: RequestStatus.FAILED }, + error: { loadingHandouts: true }, + })); } }; } @@ -102,10 +132,16 @@ export function editCourseHandoutsQuery(courseId, data) { const courseHandouts = await editHandouts(courseId, data); dispatch(editCourseHandouts(courseHandouts)); dispatch(hideProcessingNotification()); - dispatch(updateSavingStatuses({ createCourseUpdateQuery: RequestStatus.SUCCESSFUL })); + dispatch(updateSavingStatuses({ + status: { createCourseUpdateQuery: RequestStatus.SUCCESSFUL }, + error: { savingHandouts: false }, + })); } catch (error) { dispatch(hideProcessingNotification()); - dispatch(updateSavingStatuses({ createCourseUpdateQuery: RequestStatus.FAILED })); + dispatch(updateSavingStatuses({ + status: { createCourseUpdateQuery: RequestStatus.FAILED }, + error: { savingHandouts: true }, + })); } }; } diff --git a/src/course-updates/hooks.jsx b/src/course-updates/hooks.jsx index 05f80fe03..0482af6f2 100644 --- a/src/course-updates/hooks.jsx +++ b/src/course-updates/hooks.jsx @@ -98,7 +98,7 @@ const useCourseUpdates = ({ courseId }) => { courseHandouts, courseUpdatesInitialValues, isMainFormOpen: isUpdateFormOpen && requestType !== REQUEST_TYPES.edit_update, - isInnerFormOpen: (id) => isUpdateFormOpen && currentUpdate.id === id && requestType === REQUEST_TYPES.edit_update, + isInnerFormOpen: (id) => (isUpdateFormOpen && currentUpdate.id === id && requestType === REQUEST_TYPES.edit_update), isUpdateFormOpen, isDeleteModalOpen, closeUpdateForm, diff --git a/src/course-updates/messages.js b/src/course-updates/messages.js index a04a4b5ce..a148972ae 100644 --- a/src/course-updates/messages.js +++ b/src/course-updates/messages.js @@ -4,18 +4,87 @@ const messages = defineMessages({ headingTitle: { id: 'course-authoring.course-updates.header.title', defaultMessage: 'Course updates', + description: 'Title for page', }, headingSubtitle: { id: 'course-authoring.course-updates.header.subtitle', defaultMessage: 'Content', + description: 'Subtitle for page', }, sectionInfo: { id: 'course-authoring.course-updates.section-info', defaultMessage: 'Use course updates to notify students of important dates or exams, highlight particular discussions in the forums, announce schedule changes, and respond to student questions.', + description: 'Message describing the use of course updates in a course', }, newUpdateButton: { id: 'course-authoring.course-updates.actions.new-update', defaultMessage: 'New update', + description: 'Button label for header button to add a new course update', + }, + firstUpdateButton: { + id: 'course-authoring.course-updates.actions.first-update', + defaultMessage: 'Add first update', + description: 'Button label for button to add first course update', + }, + noCourseUpdates: { + id: 'course-authoring.course-updates.actions.first-update-message', + defaultMessage: 'You have not added any updates to this course yet.', + description: 'Message to notify user that they do not have any existing course updates', + }, + loadingUpdatesErrorTitle: { + id: 'course-authoring.course-updates.error.loading-updates.title', + defaultMessage: 'Failed to load course updates', + description: 'Alert title for loading updates error alert', + }, + loadingUpdatesErrorDescription: { + id: 'course-authoring.course-updates.error.loading-updates.description', + defaultMessage: 'Failed to load course updates for {courseId}. Please try again later.', + description: 'Alert body message for loading course update errors', + }, + loadingHandoutsErrorTitle: { + id: 'course-authoring.course-updates.error.loading-handouts.title', + defaultMessage: 'Failed to load course handouts', + description: 'Alert title for loading handouts error alert', + }, + loadingHandoutsErrorDescription: { + id: 'course-authoring.course-updates.error.loading-handouts.description', + defaultMessage: 'Failed to load course updates for {courseId}. Please try again later.', + description: 'Alert body message for loading course handout errors', + }, + savingUpdatesErrorTitle: { + id: 'course-authoring.course-updates.error.saving-updates.title', + defaultMessage: 'Failed to save course update', + description: 'Alert title for saving updates error alert', + }, + savingUpdatesErrorDescription: { + id: 'course-authoring.course-updates.error.saving-updates.description', + defaultMessage: 'Failed to save recent changes to course update. Please try again later.', + description: 'Alert body message for saving edits to course update errors', + }, + savingNewUpdateErrorAlertDescription: { + id: 'course-authoring.course-updates.error.saving-new-updates.description', + defaultMessage: 'Failed to save new course update. Please try again later.', + description: 'Alert body message for saving new course update errors', + }, + savingHandoutsErrorTitle: { + id: 'course-authoring.course-updates.error.saving-handouts.title', + defaultMessage: 'Failed to save course handouts', + description: 'Alert title for saving handouts error alert', + }, + savingHandoutsErrorDescription: { + id: 'course-authoring.course-updates.error.saving-handouts.description', + defaultMessage: 'Failed to save recent changes to course handouts. Please try again later.', + description: 'Alert body message for saving course handout errors', + }, + deletingUpdatesErrorTitle: { + id: 'course-authoring.course-updates.error.deleting-updates.title', + defaultMessage: 'Failed to delete course update', + description: 'Alert title for deleting update error alert', + }, + deletingUpdatesErrorDescription: { + id: 'course-authoring.course-updates.error.deleting-updates.description', + defaultMessage: 'Failed to delete selected course update. Please try again later.', + description: 'Alert body message for deleting course update errors', }, }); diff --git a/src/index.scss b/src/index.scss index 5409c7085..912b40933 100644 --- a/src/index.scss +++ b/src/index.scss @@ -51,3 +51,11 @@ div.xblock-highlight { box-shadow: unset; } } + +body { + background-color: $light-200; + + .editor-page { + background-color: $light-100; + } +}