diff --git a/src/course-unit/CourseUnit.jsx b/src/course-unit/CourseUnit.jsx index 8a117abab3..b2cda53184 100644 --- a/src/course-unit/CourseUnit.jsx +++ b/src/course-unit/CourseUnit.jsx @@ -13,6 +13,7 @@ import getPageHeadTitle from '../generic/utils'; import AlertMessage from '../generic/alert-message'; import ProcessingNotification from '../generic/processing-notification'; import InternetConnectionAlert from '../generic/internet-connection-alert'; +import ConnectionErrorAlert from '../generic/ConnectionErrorAlert'; import Loading from '../generic/Loading'; import AddComponent from './add-component/AddComponent'; import CourseXBlock from './course-xblock/CourseXBlock'; @@ -32,6 +33,7 @@ const CourseUnit = ({ courseId }) => { sequenceId, unitTitle, isQueryPending, + sequenceStatus, savingStatus, isTitleEditFormOpen, isErrorAlert, @@ -57,6 +59,14 @@ const CourseUnit = ({ courseId }) => { return ; } + if (sequenceStatus === RequestStatus.FAILED) { + return ( + + + + ); + } + return ( <> diff --git a/src/course-unit/__mocks__/courseSectionVertical.js b/src/course-unit/__mocks__/courseSectionVertical.js index ceea70fd55..67d57c2e62 100644 --- a/src/course-unit/__mocks__/courseSectionVertical.js +++ b/src/course-unit/__mocks__/courseSectionVertical.js @@ -289,6 +289,10 @@ module.exports = { }, }, ], + course_sequence_ids: [ + 'block-v1:edx+876+2030+type@sequential+block@297321078a0f4c26a50d671ed87642a6', + 'block-v1:edx+876+2030+type@sequential+block@4e91bdfefd8e4173a03d19c4d91e1936', + ], xblock_info: { id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@867dddb6f55d410caaa9c1eb9c6743ec', display_name: 'Getting Started', diff --git a/src/course-unit/course-sequence/hooks.js b/src/course-unit/course-sequence/hooks.js index 043a693fff..28035e1afd 100644 --- a/src/course-unit/course-sequence/hooks.js +++ b/src/course-unit/course-sequence/hooks.js @@ -3,20 +3,14 @@ import { useLayoutEffect, useRef, useState } from 'react'; import { useWindowSize } from '@openedx/paragon'; import { useModel } from '../../generic/model-store'; -import { - getCourseSectionVertical, - getCourseUnit, - sequenceIdsSelector, -} from '../data/selectors'; +import { getCourseSectionVertical, getSequenceIds } from '../data/selectors'; -export function useSequenceNavigationMetadata(currentSequenceId, currentUnitId) { - const sequenceIds = useSelector(sequenceIdsSelector); +export function useSequenceNavigationMetadata(courseId, currentSequenceId, currentUnitId) { const { nextUrl, prevUrl } = useSelector(getCourseSectionVertical); const sequence = useModel('sequences', currentSequenceId); - const { courseId } = useSelector(getCourseUnit); const isFirstUnit = !prevUrl; const isLastUnit = !nextUrl; - + const sequenceIds = useSelector(getSequenceIds); const sequenceIndex = sequenceIds.indexOf(currentSequenceId); const unitIndex = sequence.unitIds.indexOf(currentUnitId); diff --git a/src/course-unit/course-sequence/sequence-navigation/SequenceNavigation.jsx b/src/course-unit/course-sequence/sequence-navigation/SequenceNavigation.jsx index e7a47ec748..a2fbe55d4e 100644 --- a/src/course-unit/course-sequence/sequence-navigation/SequenceNavigation.jsx +++ b/src/course-unit/course-sequence/sequence-navigation/SequenceNavigation.jsx @@ -20,6 +20,7 @@ import SequenceNavigationTabs from './SequenceNavigationTabs'; const SequenceNavigation = ({ intl, + courseId, unitId, sequenceId, className, @@ -28,7 +29,7 @@ const SequenceNavigation = ({ const sequenceStatus = useSelector(getSequenceStatus); const { isFirstUnit, isLastUnit, nextLink, previousLink, - } = useSequenceNavigationMetadata(sequenceId, unitId); + } = useSequenceNavigationMetadata(courseId, sequenceId, unitId); const sequence = useModel('sequences', sequenceId); const shouldDisplayNotificationTriggerInSequence = useWindowSize().width < breakpoints.small.minWidth; @@ -104,6 +105,7 @@ const SequenceNavigation = ({ SequenceNavigation.propTypes = { intl: intlShape.isRequired, + courseId: PropTypes.string.isRequired, unitId: PropTypes.string, className: PropTypes.string, sequenceId: PropTypes.string, diff --git a/src/course-unit/data/api.js b/src/course-unit/data/api.js index f7adc33b47..6520d1e1de 100644 --- a/src/course-unit/data/api.js +++ b/src/course-unit/data/api.js @@ -3,23 +3,13 @@ import { camelCaseObject, getConfig } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { PUBLISH_TYPES } from '../constants'; -import { - normalizeLearningSequencesData, - normalizeMetadata, - normalizeCourseHomeCourseMetadata, - appendBrowserTimezoneToUrl, - normalizeCourseSectionVerticalData, -} from './utils'; +import { normalizeCourseSectionVerticalData } from './utils'; const getStudioBaseUrl = () => getConfig().STUDIO_BASE_URL; -const getLmsBaseUrl = () => getConfig().LMS_BASE_URL; export const getCourseUnitApiUrl = (itemId) => `${getStudioBaseUrl()}/xblock/container/${itemId}`; export const getXBlockBaseApiUrl = (itemId) => `${getStudioBaseUrl()}/xblock/${itemId}`; export const getCourseSectionVerticalApiUrl = (itemId) => `${getStudioBaseUrl()}/api/contentstore/v1/container_handler/${itemId}`; -export const getLearningSequencesOutlineApiUrl = (courseId) => `${getLmsBaseUrl()}/api/learning_sequences/v1/course_outline/${courseId}`; -export const getCourseMetadataApiUrl = (courseId) => `${getLmsBaseUrl()}/api/courseware/course/${courseId}`; -export const getCourseHomeCourseMetadataApiUrl = (courseId) => `${getLmsBaseUrl()}/api/course_home/course_metadata/${courseId}`; export const getCourseVerticalChildrenApiUrl = (itemId) => `${getStudioBaseUrl()}/api/contentstore/v1/container/vertical/${itemId}/children`; export const postXBlockBaseApiUrl = () => `${getStudioBaseUrl()}/xblock/`; @@ -65,45 +55,6 @@ export async function getCourseSectionVerticalData(unitId) { return normalizeCourseSectionVerticalData(data); } -/** - * Retrieves the outline of learning sequences for a specific course. - * @param {string} courseId - The ID of the course. - * @returns {Promise} A Promise that resolves to the normalized learning sequences outline data. - */ -export async function getLearningSequencesOutline(courseId) { - const { href } = new URL(getLearningSequencesOutlineApiUrl(courseId)); - const { data } = await getAuthenticatedHttpClient().get(href, {}); - - return normalizeLearningSequencesData(data); -} - -/** - * Retrieves metadata for a specific course. - * @param {string} courseId - The ID of the course. - * @returns {Promise} A Promise that resolves to the normalized course metadata. - */ -export async function getCourseMetadata(courseId) { - let courseMetadataApiUrl = getCourseMetadataApiUrl(courseId); - courseMetadataApiUrl = appendBrowserTimezoneToUrl(courseMetadataApiUrl); - const metadata = await getAuthenticatedHttpClient().get(courseMetadataApiUrl); - - return normalizeMetadata(metadata); -} - -/** - * Retrieves metadata for a course's home page. - * @param {string} courseId - The ID of the course. - * @param {string} rootSlug - The root slug for the course. - * @returns {Promise} A Promise that resolves to the normalized course home page metadata. - */ -export async function getCourseHomeCourseMetadata(courseId, rootSlug) { - let courseHomeCourseMetadataApiUrl = getCourseHomeCourseMetadataApiUrl(courseId); - courseHomeCourseMetadataApiUrl = appendBrowserTimezoneToUrl(courseHomeCourseMetadataApiUrl); - const { data } = await getAuthenticatedHttpClient().get(courseHomeCourseMetadataApiUrl); - - return normalizeCourseHomeCourseMetadata(data, rootSlug); -} - /** * Creates a new course XBlock. * @param {Object} options - The options for creating the XBlock. diff --git a/src/course-unit/data/selectors.js b/src/course-unit/data/selectors.js index 5fa52ac1b4..16619548ff 100644 --- a/src/course-unit/data/selectors.js +++ b/src/course-unit/data/selectors.js @@ -1,30 +1,9 @@ -import { createSelector } from '@reduxjs/toolkit'; - -import { RequestStatus } from '../../data/constants'; - export const getCourseUnitData = (state) => state.courseUnit.unit; -export const getCourseUnit = (state) => state.courseUnit; export const getSavingStatus = (state) => state.courseUnit.savingStatus; export const getLoadingStatus = (state) => state.courseUnit.loadingStatus; export const getSequenceStatus = (state) => state.courseUnit.sequenceStatus; +export const getSequenceIds = (state) => state.courseUnit.courseSectionVertical.courseSequenceIds; export const getCourseSectionVertical = (state) => state.courseUnit.courseSectionVertical; -export const getCourseUnitComponentTemplates = (state) => state.courseUnit.courseSectionVertical.componentTemplates; -export const getCourseSectionVerticalLoadingStatus = (state) => state - .courseUnit.loadingStatus.courseSectionVerticalLoadingStatus; -export const getCourseStatus = state => state.courseUnit.courseStatus; -export const getCoursewareMeta = state => state.models.coursewareMeta; -export const getSections = state => state.models.sections; -export const getCourseId = state => state.courseDetail.courseId; -export const getSequenceId = state => state.courseUnit.sequenceId; -export const getCourseVerticalChildren = state => state.courseUnit.courseVerticalChildren; -export const sequenceIdsSelector = createSelector( - [getCourseStatus, getCoursewareMeta, getSections, getCourseId], - (courseStatus, coursewareMeta, sections, courseId) => { - if (courseStatus !== RequestStatus.SUCCESSFUL) { - return []; - } - - const sectionIds = coursewareMeta[courseId].sectionIds || []; - return sectionIds.flatMap(sectionId => sections[sectionId].sequenceIds); - }, -); +export const getCourseId = (state) => state.courseDetail.courseId; +export const getSequenceId = (state) => state.courseUnit.sequenceId; +export const getCourseVerticalChildren = (state) => state.courseUnit.courseVerticalChildren; diff --git a/src/course-unit/data/slice.js b/src/course-unit/data/slice.js index bd4066afcc..0134fcb054 100644 --- a/src/course-unit/data/slice.js +++ b/src/course-unit/data/slice.js @@ -52,22 +52,6 @@ const slice = createSlice({ state.sequenceStatus = RequestStatus.FAILED; state.sequenceMightBeUnit = payload.sequenceMightBeUnit || false; }, - fetchCourseRequest: (state, { payload }) => { - state.courseId = payload.courseId; - state.courseStatus = RequestStatus.IN_PROGRESS; - }, - fetchCourseSuccess: (state, { payload }) => { - state.courseId = payload.courseId; - state.courseStatus = RequestStatus.SUCCESSFUL; - }, - fetchCourseFailure: (state, { payload }) => { - state.courseId = payload.courseId; - state.courseStatus = RequestStatus.FAILED; - }, - fetchCourseDenied: (state, { payload }) => { - state.courseId = payload.courseId; - state.courseStatus = RequestStatus.DENIED; - }, fetchCourseSectionVerticalDataSuccess: (state, { payload }) => { state.courseSectionVertical = payload; }, @@ -122,10 +106,6 @@ export const { fetchSequenceRequest, fetchSequenceSuccess, fetchSequenceFailure, - fetchCourseRequest, - fetchCourseSuccess, - fetchCourseFailure, - fetchCourseDenied, fetchCourseSectionVerticalDataSuccess, updateLoadingCourseSectionVerticalDataStatus, changeEditTitleFormOpen, diff --git a/src/course-unit/data/thunk.js b/src/course-unit/data/thunk.js index 23b39937a1..e18a0cc6d6 100644 --- a/src/course-unit/data/thunk.js +++ b/src/course-unit/data/thunk.js @@ -1,4 +1,3 @@ -import { logError, logInfo } from '@edx/frontend-platform/logging'; import { camelCaseObject } from '@edx/frontend-platform'; import { @@ -7,15 +6,10 @@ import { } from '../../generic/processing-notification/data/slice'; import { RequestStatus } from '../../data/constants'; import { NOTIFICATION_MESSAGES } from '../../constants'; -import { - addModel, updateModel, updateModels, updateModelsMap, addModelsMap, -} from '../../generic/model-store'; +import { updateModel, updateModels } from '../../generic/model-store'; import { getCourseUnitData, editUnitDisplayName, - getCourseMetadata, - getLearningSequencesOutline, - getCourseHomeCourseMetadata, getCourseSectionVerticalData, createCourseXblock, getCourseVerticalChildren, @@ -30,10 +24,6 @@ import { fetchSequenceRequest, fetchSequenceFailure, fetchSequenceSuccess, - fetchCourseRequest, - fetchCourseSuccess, - fetchCourseDenied, - fetchCourseFailure, fetchCourseSectionVerticalDataSuccess, updateLoadingCourseSectionVerticalDataStatus, updateLoadingCourseXblockStatus, @@ -146,88 +136,6 @@ export function editCourseUnitVisibilityAndData(itemId, type, isVisible) { }; } -export function fetchCourse(courseId) { - return async (dispatch) => { - dispatch(fetchCourseRequest({ courseId })); - Promise.allSettled([ - getCourseMetadata(courseId), - getLearningSequencesOutline(courseId), - getCourseHomeCourseMetadata(courseId, 'courseware'), - ]).then(([ - courseMetadataResult, - learningSequencesOutlineResult, - courseHomeMetadataResult]) => { - if (courseMetadataResult.status === 'fulfilled') { - dispatch(addModel({ - modelType: 'coursewareMeta', - model: courseMetadataResult.value, - })); - } - - if (courseHomeMetadataResult.status === 'fulfilled') { - dispatch(addModel({ - modelType: 'courseHomeMeta', - model: { - id: courseId, - ...courseHomeMetadataResult.value, - }, - })); - } - - if (learningSequencesOutlineResult.status === 'fulfilled') { - const { courses, sections } = learningSequencesOutlineResult.value; - - // This updates the course with a sectionIds array from the Learning Sequence data. - dispatch(updateModelsMap({ - modelType: 'coursewareMeta', - modelsMap: courses, - })); - dispatch(addModelsMap({ - modelType: 'sections', - modelsMap: sections, - })); - } - - const fetchedMetadata = courseMetadataResult.status === 'fulfilled'; - const fetchedCourseHomeMetadata = courseHomeMetadataResult.status === 'fulfilled'; - const fetchedOutline = learningSequencesOutlineResult.status === 'fulfilled'; - - // Log errors for each request if needed. Outline failures may occur - // even if the course metadata request is successful - if (!fetchedOutline) { - const { response } = learningSequencesOutlineResult.reason; - if (response && response.status === 403) { - // 403 responses are normal - they happen when the learner is logged out. - // We'll redirect them in a moment to the outline tab by calling fetchCourseDenied() below. - logInfo(learningSequencesOutlineResult.reason); - } else { - logError(learningSequencesOutlineResult.reason); - } - } - if (!fetchedMetadata) { - logError(courseMetadataResult.reason); - } - if (!fetchedCourseHomeMetadata) { - logError(courseHomeMetadataResult.reason); - } - if (fetchedMetadata && fetchedCourseHomeMetadata) { - if (courseHomeMetadataResult.value.courseAccess.hasAccess && fetchedOutline) { - // User has access - dispatch(fetchCourseSuccess({ courseId })); - return; - } - // User either doesn't have access or only has partial access - // (can't access course blocks) - dispatch(fetchCourseDenied({ courseId })); - return; - } - - // Definitely an error happening - dispatch(fetchCourseFailure({ courseId })); - }); - }; -} - export function createNewCourseXBlock(body, callback, blockId) { return async (dispatch) => { dispatch(updateLoadingCourseXblockStatus({ status: RequestStatus.IN_PROGRESS })); diff --git a/src/course-unit/data/utils.js b/src/course-unit/data/utils.js index afd6e0a03a..a37faaa4db 100644 --- a/src/course-unit/data/utils.js +++ b/src/course-unit/data/utils.js @@ -3,196 +3,7 @@ import { camelCaseObject } from '@edx/frontend-platform'; import { NOTIFICATION_MESSAGES } from '../../constants'; import { PUBLISH_TYPES } from '../constants'; -export function getTimeOffsetMillis(headerDate, requestTime, responseTime) { - // Time offset computation should move down into the HttpClient wrapper to maintain a global time correction reference - // Requires 'Access-Control-Expose-Headers: Date' on the server response per https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#access-control-expose-headers - - let timeOffsetMillis = 0; - if (headerDate !== undefined) { - const headerTime = Date.parse(headerDate); - const roundTripMillis = requestTime - responseTime; - const localTime = responseTime - (roundTripMillis / 2); // Roughly compensate for transit time - timeOffsetMillis = headerTime - localTime; - } - - return timeOffsetMillis; -} - -export function normalizeMetadata(metadata) { - const requestTime = Date.now(); - const responseTime = requestTime; - const { data, headers } = metadata; - return { - accessExpiration: camelCaseObject(data.access_expiration), - canShowUpgradeSock: data.can_show_upgrade_sock, - contentTypeGatingEnabled: data.content_type_gating_enabled, - courseGoals: camelCaseObject(data.course_goals), - id: data.id, - title: data.name, - offer: camelCaseObject(data.offer), - enrollmentStart: data.enrollment_start, - enrollmentEnd: data.enrollment_end, - end: data.end, - start: data.start, - enrollmentMode: data.enrollment.mode, - isEnrolled: data.enrollment.is_active, - license: data.license, - userTimezone: data.user_timezone, - showCalculator: data.show_calculator, - notes: camelCaseObject(data.notes), - marketingUrl: data.marketing_url, - celebrations: camelCaseObject(data.celebrations), - userHasPassingGrade: data.user_has_passing_grade, - courseExitPageIsActive: data.course_exit_page_is_active, - certificateData: camelCaseObject(data.certificate_data), - entranceExamData: camelCaseObject(data.entrance_exam_data), - timeOffsetMillis: getTimeOffsetMillis(headers && headers.date, requestTime, responseTime), - verifyIdentityUrl: data.verify_identity_url, - verificationStatus: data.verification_status, - linkedinAddToProfileUrl: data.linkedin_add_to_profile_url, - relatedPrograms: camelCaseObject(data.related_programs), - userNeedsIntegritySignature: data.user_needs_integrity_signature, - canAccessProctoredExams: data.can_access_proctored_exams, - learningAssistantEnabled: data.learning_assistant_enabled, - }; -} - -export const appendBrowserTimezoneToUrl = (url) => { - const browserTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; - const urlObject = new URL(url); - if (browserTimezone) { - urlObject.searchParams.append('browser_timezone', browserTimezone); - } - return urlObject.href; -}; - -export function normalizeSequenceMetadata(sequence) { - return { - sequence: { - id: sequence.item_id, - blockType: sequence.tag, - unitIds: sequence.items.map(unit => unit.id), - bannerText: sequence.banner_text, - format: sequence.format, - title: sequence.display_name, - /* - Example structure of gated_content when prerequisites exist: - { - prereq_id: 'id of the prereq section', - prereq_url: 'unused by this frontend', - prereq_section_name: 'Name of the prerequisite section', - gated: true, - gated_section_name: 'Name of this gated section', - */ - gatedContent: camelCaseObject(sequence.gated_content), - isTimeLimited: sequence.is_time_limited, - isProctored: sequence.is_proctored, - isHiddenAfterDue: sequence.is_hidden_after_due, - // Position comes back from the server 1-indexed. Adjust here. - activeUnitIndex: sequence.position ? sequence.position - 1 : 0, - saveUnitPosition: sequence.save_position, - showCompletion: sequence.show_completion, - allowProctoringOptOut: sequence.allow_proctoring_opt_out, - }, - units: sequence.items.map(unit => ({ - id: unit.id, - sequenceId: sequence.item_id, - bookmarked: unit.bookmarked, - complete: unit.complete, - title: unit.page_title, - contentType: unit.type, - graded: unit.graded, - containsContentTypeGatedContent: unit.contains_content_type_gated_content, - })), - }; -} - -export function normalizeLearningSequencesData(learningSequencesData) { - const models = { - courses: {}, - sections: {}, - sequences: {}, - }; - - const now = new Date(); - function isReleased(block) { - // We check whether the backend marks this as accessible because staff users are granted access anyway. - // Note that sections don't have the `accessible` field and will just be checking `effective_start`. - return block.accessible || !block.effective_start || now >= Date.parse(block.effective_start); - } - - // Sequences - Object.entries(learningSequencesData.outline.sequences).forEach(([seqId, sequence]) => { - if (!isReleased(sequence)) { - return; // Don't let the learner see unreleased sequences - } - - models.sequences[seqId] = { - id: seqId, - title: sequence.title, - }; - }); - - // Sections - learningSequencesData.outline.sections.forEach(section => { - // Filter out any ignored sequences (e.g. unreleased sequences) - const availableSequenceIds = section.sequence_ids.filter(seqId => seqId in models.sequences); - - // If we are unreleased and already stripped out all our children, just don't show us at all. - // (We check both release date and children because children will exist for an unreleased section even for staff, - // so we still want to show this section.) - if (!isReleased(section) && !availableSequenceIds.length) { - return; - } - - models.sections[section.id] = { - id: section.id, - title: section.title, - sequenceIds: availableSequenceIds, - courseId: learningSequencesData.course_key, - }; - - // Add back-references to this section for all child sequences. - availableSequenceIds.forEach(childSeqId => { - models.sequences[childSeqId].sectionId = section.id; - }); - }); - - // Course - models.courses[learningSequencesData.course_key] = { - id: learningSequencesData.course_key, - title: learningSequencesData.title, - sectionIds: Object.entries(models.sections).map(([sectionId]) => sectionId), - - // Scan through all the sequences and look for ones that aren't released yet. - hasScheduledContent: Object.values(learningSequencesData.outline.sequences).some(seq => !isReleased(seq)), - }; - - return models; -} - -/** - * Tweak the metadata for consistency - * @param metadata the data to normalize - * @param rootSlug either 'courseware' or 'outline' depending on the context - * @returns {Object} The normalized metadata - */ -export function normalizeCourseHomeCourseMetadata(metadata, rootSlug) { - const data = camelCaseObject(metadata); - return { - ...data, - tabs: data.tabs.map(tab => ({ - // The API uses "courseware" as a slug for both courseware and the outline tab. - // If needed, we switch it to "outline" here for - // use within the MFE to differentiate between course home and courseware. - slug: tab.tabId === 'courseware' ? rootSlug : tab.tabId, - title: tab.title, - url: tab.url, - })), - isMasquerading: data.originalUserIsStaff && !data.isStaff, - }; -} - +// eslint-disable-next-line import/prefer-default-export export function normalizeCourseSectionVerticalData(metadata) { const data = camelCaseObject(metadata); return { diff --git a/src/course-unit/hooks.jsx b/src/course-unit/hooks.jsx index dea7599b89..d1d1edc7bc 100644 --- a/src/course-unit/hooks.jsx +++ b/src/course-unit/hooks.jsx @@ -7,7 +7,6 @@ import { createNewCourseXBlock, fetchCourseUnitQuery, editCourseItemQuery, - fetchCourse, fetchCourseSectionVerticalData, fetchCourseVerticalChildrenData, deleteUnitItemQuery, @@ -19,6 +18,7 @@ import { getCourseUnitData, getLoadingStatus, getSavingStatus, + getSequenceStatus, } from './data/selectors'; import { changeEditTitleFormOpen, updateQueryPendingStatus } from './data/slice'; @@ -31,6 +31,7 @@ export const useCourseUnit = ({ courseId, blockId }) => { const courseUnit = useSelector(getCourseUnitData); const savingStatus = useSelector(getSavingStatus); const loadingStatus = useSelector(getLoadingStatus); + const sequenceStatus = useSelector(getSequenceStatus); const { draftPreviewLink, publishedPreviewLink } = useSelector(getCourseSectionVertical); const courseVerticalChildren = useSelector(getCourseVerticalChildren); const navigate = useNavigate(); @@ -87,7 +88,7 @@ export const useCourseUnit = ({ courseId, blockId }) => { useEffect(() => { if (savingStatus === RequestStatus.SUCCESSFUL) { - dispatch(updateQueryPendingStatus(false)); + dispatch(updateQueryPendingStatus(true)); } else if (savingStatus === RequestStatus.FAILED && !hasInternetConnectionError) { toggleErrorAlert(true); } @@ -97,7 +98,6 @@ export const useCourseUnit = ({ courseId, blockId }) => { dispatch(fetchCourseUnitQuery(blockId)); dispatch(fetchCourseSectionVerticalData(blockId, sequenceId)); dispatch(fetchCourseVerticalChildrenData(blockId)); - dispatch(fetchCourse(courseId)); handleNavigate(sequenceId); }, [courseId, blockId, sequenceId]); @@ -106,6 +106,7 @@ export const useCourseUnit = ({ courseId, blockId }) => { sequenceId, courseUnit, unitTitle, + sequenceStatus, savingStatus, isQueryPending, isErrorAlert,