Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
02089e4
refactor: prepare helpers
navinkarkera May 4, 2026
b46dd26
refactor: extract selection state builder
navinkarkera May 4, 2026
27f3cce
refactor: Add CourseOutlineStateContext facade
navinkarkera May 4, 2026
1c07436
refactor(course-outline): move outline reads to state context
navinkarkera May 4, 2026
83b662f
fix(course-outline): decouple outline selection from menu state
navinkarkera May 4, 2026
2368ab0
refactor: move current-item and editability reads out of sidebar context
navinkarkera May 4, 2026
5815af1
refactor: Remove course outline dependency from `CourseAuthoringContext`
navinkarkera May 4, 2026
de32267
refactor: runtime fetch swapped from thunk effect to React Query
navinkarkera May 4, 2026
25a5267
refactor: visible tree ownership moved into `CourseOutlineStateContext`
navinkarkera May 4, 2026
5b683b2
refactor: get sections from state instead of context
navinkarkera May 5, 2026
97f303e
refactor: move drag-drop to state
navinkarkera May 5, 2026
a4495c6
test: fix outline tests
navinkarkera May 5, 2026
bfc93f0
refactor(course-outline): finish PR 8 — move visible tree ownership t…
navinkarkera May 5, 2026
eb50091
fix(course-outline): keep reorder preview stable
navinkarkera May 5, 2026
b96b9ac
refactor(course-outline): move index query sync to state seam
navinkarkera May 5, 2026
ba4cb06
fix(course-outline): sync reorder cache after drag drop
navinkarkera May 6, 2026
6f9ea76
refactor(course-outline): centralize mutation handlers
navinkarkera May 7, 2026
8e01275
refactor(course-outline): move section reorder to query mutation
navinkarkera May 7, 2026
1a481c7
refactor(course-outline): move subsection reorder to query mutation
navinkarkera May 7, 2026
dae6124
fix(course-outline): prevent stale outline data across course navigation
navinkarkera May 8, 2026
ce9af98
refactor(course-outline): remove delete redux facade sync
navinkarkera May 8, 2026
db6de49
refactor(course-outline): flatten delete cache tree updates
navinkarkera May 8, 2026
ceb2d11
test(course-outline): assert delete cache updates
navinkarkera May 8, 2026
3c75947
refactor(course-outline): migrate tests off redux facade and prune de…
navinkarkera May 9, 2026
90cb915
fix(course-outline): pass explicit republish type for unit configure
navinkarkera May 9, 2026
57d9b0b
refactor(course-outline): remove unused thunk seam file
navinkarkera May 9, 2026
a5b75ec
refactor(course-outline): remove redux outline slice seam
navinkarkera May 11, 2026
6198a89
fix(course-outline): surface reindex error alert on failure
navinkarkera May 11, 2026
af96bde
refactor(course-outline): extract reorder hook and tighten types
navinkarkera May 11, 2026
3315899
refactor(course-outline): extract outline mutation hook
navinkarkera May 11, 2026
09d0ee0
refactor(course-outline): extract outline status/query hook
navinkarkera May 11, 2026
6e31f94
refactor(course-outline): move action and modal state to seam
navinkarkera May 12, 2026
71e3c05
refactor(course-outline): rename state seam as primary context API
navinkarkera May 12, 2026
ecfe7dc
refactor(course-outline): consolidate to single outline context file
navinkarkera May 12, 2026
07c2a4a
fix(course-outline): clean cache sync and configure target
navinkarkera May 13, 2026
67e2b5e
refactor(course-outline): use React Query status for outline index
navinkarkera May 13, 2026
3db7792
fix(course-outline): harden outline error and reorder flows
navinkarkera May 14, 2026
9fe5a2d
chore: remove unnecessary comments
navinkarkera May 14, 2026
9a291a5
fix(course-outline): harden delete and trim reorder state
navinkarkera May 15, 2026
2cdbdeb
refactor(course-outline): move mutations to api hooks and simplify co…
navinkarkera May 15, 2026
ad06fbf
fix: invalid course block api url
navinkarkera May 15, 2026
8f6842e
test(course-outline): add apiHooks coverage for mutations
navinkarkera May 15, 2026
8fd92b1
fix(course-outline): harden add, delete, and reorder state
navinkarkera May 18, 2026
cf2d880
refactor(course-outline): centralize outline actions in context
navinkarkera May 18, 2026
613f2e4
refactor(course-outline): localize add and duplicate hooks
navinkarkera May 18, 2026
e033721
fix(course-outline): avoid delete self-refetch
navinkarkera May 18, 2026
63477ff
fix: tests
navinkarkera May 19, 2026
5c0297f
chore: lint fix
navinkarkera May 19, 2026
43d4c88
test(course-outline): trim redundant test coverage
navinkarkera May 19, 2026
7dc3d55
test(course-outline): trim reorder hook tests
navinkarkera May 19, 2026
0a96926
refactor(course-outline): inline tiny state helpers
navinkarkera May 19, 2026
fe62a59
refactor(course-outline): inline page alert helper
navinkarkera May 19, 2026
5950587
refactor(course-outline): extract tree, modal, and action hooks
navinkarkera Jun 1, 2026
ecac0f1
fix(course-outline): type modal action payloads
navinkarkera Jun 1, 2026
e1d8456
refactor(course-outline): trim modal and reorder helpers
navinkarkera Jun 1, 2026
48afafb
fix(course-outline): pass courseId to reorder hooks
navinkarkera Jun 1, 2026
63a3dd9
fix(course-outline): harden modal mutation flows
navinkarkera Jun 1, 2026
389b2e4
refactor(course-outline): dedupe reorder mutation flows
navinkarkera Jun 1, 2026
e3fa33b
refactor(course-outline): move modal JSX to CourseOutline
navinkarkera Jun 1, 2026
3d50938
refactor(course-outline): use query hooks for outline status
navinkarkera Jun 1, 2026
c188ae0
refactor(course-outline): expose module interfaces and trim reorder API
navinkarkera Jun 2, 2026
0881bfb
refactor: create simple helpers
navinkarkera Jun 2, 2026
8f431bf
refactor(course-outline): simplify outline modal and data helpers
navinkarkera Jun 4, 2026
f12143e
refactor(course-outline): centralize outline query keys
navinkarkera Jun 4, 2026
3a3465e
refactor(course-outline): centralize outline operations
navinkarkera Jun 4, 2026
34768e6
test(course-outline): build shared test foundation
navinkarkera Jun 4, 2026
bba525f
test(course-outline): align outline helper child info
navinkarkera Jun 4, 2026
69cc5b2
test(course-outline): migrate outline tests to factory fixtures
navinkarkera Jun 4, 2026
a39a2b5
test(course-outline): migrate more outline tests to factory data
navinkarkera Jun 4, 2026
2fc3c7a
test(course-outline): migrate configure tests to factory data
navinkarkera Jun 4, 2026
cca10b7
test(course-outline): finish shared test foundation
navinkarkera Jun 4, 2026
c875e9b
test(course-outline): deduplicate card tests
navinkarkera Jun 4, 2026
37a3f07
refactor(course-outline): render outline nodes recursively
navinkarkera Jun 5, 2026
cc57925
refactor(course-outline): simplify sidebar modal state
navinkarkera Jun 5, 2026
e0e5b00
refactor(course-outline): clean up outline polish items
navinkarkera Jun 5, 2026
4d980a0
fix: drag-drop flicker
navinkarkera Jun 5, 2026
73f3416
fix: lint issues
navinkarkera Jun 6, 2026
b1c0121
fix: types
navinkarkera Jun 6, 2026
6c3258f
fix(course-outline): prevent escape from saving titles
navinkarkera Jun 6, 2026
67c373d
refactor(course-outline): simplify outline node tests
navinkarkera Jun 8, 2026
d52f26f
chore: use @src
navinkarkera Jun 8, 2026
2b5e8dc
refactor: proper types
navinkarkera Jun 8, 2026
4e16b77
fix(course-outline): correct library drag targets
navinkarkera Jun 8, 2026
235e8c2
refactor(course-outline): merge reorder helpers into drag utils
navinkarkera Jun 8, 2026
5881cb7
fix: lint issues
navinkarkera Jun 8, 2026
6649b2a
test: fix test
navinkarkera Jun 8, 2026
3fdc412
chore: remove unnecessary comment
navinkarkera Jun 8, 2026
d55652d
test: phase 2 cleanup - trim trivial assertions, merge duplicates, si…
navinkarkera Jun 8, 2026
2fd6951
test: fix useHighlightsModal mock - add useEnableCourseHighlightsEmai…
navinkarkera Jun 8, 2026
c0ddcbf
test(course-outline): remove low-value assertions
navinkarkera Jun 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions src/CourseAuthoringContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@ import {
useMemo,
} from 'react';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { useSelector } from 'react-redux';
import { useNavigate } from 'react-router';
import { useToggleWithValue } from '@src/hooks';
import { type UnitXBlock, type XBlock } from '@src/data/types';
import { CourseDetailsData } from './data/api';
import { useCourseDetails, useWaffleFlags } from './data/apiHooks';
import { RequestStatusType } from './data/constants';
import { getOutlineIndexData } from './course-outline/data/selectors';

export type ModalState = {
value?: XBlock | UnitXBlock;
Expand All @@ -23,7 +21,6 @@ export type ModalState = {
export type CourseAuthoringContextData = {
/** The ID of the current course */
courseId: string;
courseUsageKey: string;
courseDetails?: CourseDetailsData;
courseDetailStatus: RequestStatusType;
canChangeProviders: boolean;
Expand Down Expand Up @@ -56,8 +53,6 @@ export const CourseAuthoringProvider = ({
const waffleFlags = useWaffleFlags();
const { data: courseDetails, status: courseDetailStatus } = useCourseDetails(courseId);
const canChangeProviders = getAuthenticatedUser().administrator || new Date(courseDetails?.start ?? 0) > new Date();
const { courseStructure } = useSelector(getOutlineIndexData);
const { id: courseUsageKey } = courseStructure || {};
const [
isUnlinkModalOpen,
currentUnlinkModalData,
Expand Down Expand Up @@ -88,7 +83,6 @@ export const CourseAuthoringProvider = ({

const context = useMemo<CourseAuthoringContextData>(() => ({
courseId,
courseUsageKey,
courseDetails,
courseDetailStatus,
canChangeProviders,
Expand All @@ -100,7 +94,6 @@ export const CourseAuthoringProvider = ({
currentUnlinkModalData,
}), [
courseId,
courseUsageKey,
courseDetails,
courseDetailStatus,
canChangeProviders,
Expand Down
296 changes: 115 additions & 181 deletions src/CourseAuthoringRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
Routes,
Route,
useParams,
Outlet,
} from 'react-router-dom';
import { getConfig } from '@edx/frontend-platform';
import { PageWrap } from '@edx/frontend-platform/react';
Expand All @@ -16,10 +17,10 @@ import { FilesPage, VideosPage } from './files-and-videos';
import { AdvancedSettings } from './advanced-settings';
import {
CourseOutline,
CourseOutlineProvider,
OutlineSidebarProvider,
OutlineSidebarPagesProvider,
} from './course-outline';
import { CourseOutlineProvider } from './course-outline/CourseOutlineContext';
import ScheduleAndDetails from './schedule-and-details';
import { GradingSettings } from './grading-settings';
import CourseTeam from './course-team/CourseTeam';
Expand All @@ -38,6 +39,13 @@ import { CourseAuthoringProvider } from './CourseAuthoringContext';
import { CourseImportProvider } from './import-page/CourseImportContext';
import { CourseExportProvider } from './export-page/CourseExportContext';

/** Layout route: renders its child routes inside PageWrap. */
const PageWrapLayout = () => (
<PageWrap>
<Outlet />
</PageWrap>
);

/**
* As of this writing, these routes are mounted at a path prefixed with the following:
*
Expand All @@ -62,208 +70,134 @@ const CourseAuthoringRoutes = () => {
throw new Error('Error: route is missing courseId.');
}

const enableVideos = getConfig().ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN === 'true';
const enableCertificates = getConfig().ENABLE_CERTIFICATE_PAGE === 'true';

return (
<CourseAuthoringProvider courseId={courseId}>
<CourseAuthoringPage>
<Routes>
<Route
path="/"
element={
<PageWrap>
<CourseOutlineProvider>
<Route element={<PageWrapLayout />}>
<Route
path="/"
element={
<CourseOutlineProvider key={courseId}>
<OutlineSidebarPagesProvider>
<OutlineSidebarProvider>
<CourseOutline />
</OutlineSidebarProvider>
</OutlineSidebarPagesProvider>
</CourseOutlineProvider>
</PageWrap>
}
/>
<Route
path="course_info"
element={
<PageWrap>
<CourseUpdates />
</PageWrap>
}
/>
<Route
path="libraries"
element={
<PageWrap>
<CourseLibraries />
</PageWrap>
}
/>
<Route
path="assets"
element={
<PageWrap>
<FilesPage />
</PageWrap>
}
/>
<Route
path="videos"
element={getConfig().ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN === 'true'
? (
<PageWrap>
<VideosPage />
</PageWrap>
)
: null}
/>
<Route
path="pages-and-resources/*"
element={
<PageWrap>
<PagesAndResources />
</PageWrap>
}
/>
<Route
path="proctored-exam-settings"
element={<Navigate replace to={`/course/${courseId}/pages-and-resources`} />}
/>
<Route
path="custom-pages/*"
element={
<PageWrap>
<CustomPages />
</PageWrap>
}
/>
<Route
path="/subsection/:subsectionId"
element={
<PageWrap>
<SubsectionUnitRedirect />
</PageWrap>
}
/>
{DECODED_ROUTES.COURSE_UNIT.map((path) => (
}
/>
<Route
key={path}
path={path}
element={
<PageWrap>
path="course_info"
element={<CourseUpdates />}
/>
<Route
path="libraries"
element={<CourseLibraries />}
/>
<Route
path="assets"
element={<FilesPage />}
/>
{enableVideos && (
<Route
path="videos"
element={<VideosPage />}
/>
)}
<Route
path="pages-and-resources/*"
element={<PagesAndResources />}
/>
<Route
path="custom-pages/*"
element={<CustomPages />}
/>
<Route
path="/subsection/:subsectionId"
element={<SubsectionUnitRedirect />}
/>
{DECODED_ROUTES.COURSE_UNIT.map((path) => (
<Route
key={path}
path={path}
element={
<IframeProvider>
<CourseUnit />
</IframeProvider>
</PageWrap>
}
}
/>
))}
<Route
path="editor/course-videos/:blockId"
element={<VideoSelectorContainer />}
/>
))}
<Route
path="editor/course-videos/:blockId"
element={
<PageWrap>
<VideoSelectorContainer />
</PageWrap>
}
/>
<Route
path="editor/:blockType/:blockId?"
element={
<PageWrap>
<EditorContainer learningContextId={courseId} />
</PageWrap>
}
/>
<Route
path="settings/details"
element={
<PageWrap>
<ScheduleAndDetails />
</PageWrap>
}
/>
<Route
path="settings/grading"
element={
<PageWrap>
<GradingSettings />
</PageWrap>
}
/>
<Route
path="course_team"
element={
<PageWrap>
<CourseTeam />
</PageWrap>
}
/>
<Route
path="group_configurations"
element={
<PageWrap>
<GroupConfigurations />
</PageWrap>
}
/>
<Route
path="settings/advanced"
element={
<PageWrap>
<AdvancedSettings />
</PageWrap>
}
/>
<Route
path="import"
element={
<PageWrap>
<Route
path="editor/:blockType/:blockId?"
element={<EditorContainer learningContextId={courseId} />}
/>
<Route
path="settings/details"
element={<ScheduleAndDetails />}
/>
<Route
path="settings/grading"
element={<GradingSettings />}
/>
<Route
path="course_team"
element={<CourseTeam />}
/>
<Route
path="group_configurations"
element={<GroupConfigurations />}
/>
<Route
path="settings/advanced"
element={<AdvancedSettings />}
/>
<Route
path="import"
element={
<CourseImportProvider>
<CourseImportPage />
</CourseImportProvider>
</PageWrap>
}
/>
<Route
path="export"
element={
<PageWrap>
}
/>
<Route
path="export"
element={
<CourseExportProvider>
<CourseExportPage />
</CourseExportProvider>
</PageWrap>
}
/>
<Route
path="optimizer"
element={
<PageWrap>
<CourseOptimizerPage />
</PageWrap>
}
/>
<Route
path="checklists"
element={
<PageWrap>
<CourseChecklist />
</PageWrap>
}
/>
<Route
path="certificates"
element={getConfig().ENABLE_CERTIFICATE_PAGE === 'true'
? (
<PageWrap>
<Certificates />
</PageWrap>
)
: null}
/>
}
/>
<Route
path="optimizer"
element={<CourseOptimizerPage />}
/>
<Route
path="checklists"
element={<CourseChecklist />}
/>
{enableCertificates && (
<Route
path="certificates"
element={<Certificates />}
/>
)}
<Route
path="textbooks"
element={<Textbooks />}
/>
</Route>
{/* Routes without PageWrap */}
<Route
path="textbooks"
element={
<PageWrap>
<Textbooks />
</PageWrap>
}
path="proctored-exam-settings"
element={<Navigate replace to={`/course/${courseId}/pages-and-resources`} />}
/>
</Routes>
</CourseAuthoringPage>
Expand Down
Loading
Loading