Skip to content

feat: add course lifecycle management UI#13

Open
eemaanamir wants to merge 5 commits intoulmo/fbrfrom
eemaan/cms-lifecycle
Open

feat: add course lifecycle management UI#13
eemaanamir wants to merge 5 commits intoulmo/fbrfrom
eemaan/cms-lifecycle

Conversation

@eemaanamir
Copy link
Copy Markdown

@eemaanamir eemaanamir commented Apr 2, 2026

Course Lifecycle Management — Frontend (Studio)

Adds a complete review/approval workflow UI to frontend-app-authoring, consuming the new lifecycle REST API. Blocks flow through Draft → For Review →
Approved → Published
at both the block and course level, with role-based action buttons, comment threads, and inline status badges throughout the course
outline, unit page, and Studio home.


Studio Home

Course card lifecycle badges

  • LifecycleBadge rendered on each course card for non-published states
  • Bulk lifecycle state fetched in a single API call via useBulkCourseAggregateStates
  • CoursesLifecycleFilterMenu dropdown to filter the course list by review state (All / Draft / For Review / Approved)
  • lifecycleFilter added to the Redux studioHomeCoursesRequestParams slice

Course Outline

Sidebar (CourseLifecycleSection)

  • Course-level aggregate state badge
  • Submit All / Approve All / Request Changes / Publish Course buttons (shown per role and state)
  • Course-level comment thread with resolve / delete support
  • Outline auto-reloads after a course publish via a useEffect ref-comparison that dispatches fetchCourseOutlineIndexQuery

Outline cards (Section / Subsection / Unit)

  • LifecycleBadge on each card showing current review state
  • Clicking the badge opens a LifecycleModal with the full review panel (badge + action buttons + comments)
  • CardHeader receives canPublish / lifecycleState / onClickLifecycle props; the Publish menu item is hidden only when the lifecycle system explicitly
    denies it (canPublish === false), preserving the default publish behaviour for blocks not enrolled in the workflow

Unit Page

  • LifecycleSection ("Unit Status") in the sidebar details tab
  • Studio's own Publish button is hidden (hidePublishButton) when a lifecycle state is present — the lifecycle section owns publishing on this page
  • Unit sidebar controls (editedOn, publishedOn, discard button) refresh after a lifecycle publish via fetchCourseSectionVerticalData dispatched through an
    onPublishSuccess callback

Data Layer (src/course-lifecycle/data/)

File Description
api.ts Fetch wrappers for all 18 REST endpoints (block state, course state, bulk states, submit/approve/request-changes/publish, comments CRUD)
apiHooks.ts React Query hooks — all queries + mutations; mutations invalidate ['lifecycle'] on success; publish hooks also invalidate courseOutlineQueryKeys.contentLibrary(...)
types.ts BlockReviewState, CourseAggregateState, BlockReviewComment, LifecycleState
api.mock.ts Mock factory functions for all three data types (used in tests)

Components (src/course-lifecycle/components/)

Component Description
LifecycleBadge State pill — Draft / Submitted for Review / Approved / Published
LifecycleSection Full review panel: badge + action buttons + comment thread; invalidates cache on hasChanges change
LifecycleModal Modal wrapper for block lifecycle management; computes effectiveState (draft with no changes → published)
LifecycleActionButtons Contextual Submit / Approve / Request Changes / Publish buttons driven by can* capability flags
CourseLifecycleSection Course-level aggregate panel for the outline sidebar
BlockCommentsPanel Collapsible comment thread per block with resolve / delete (author-gated)
CourseCommentsPanel Course-level comment thread variant

Tests (src/course-lifecycle/)

72 tests across 7 new test files, following the hook-mocking pattern used throughout the MFE:

Test file Tests Coverage
LifecycleBadge.test.tsx 5 Badge label and CSS class per state
LifecycleActionButtons.test.tsx 11 Null rendering, each button, hasChanges=false hides workflow buttons, click handlers
BlockCommentsPanel.test.tsx 13 Loading, empty, comment list, resolve/delete ownership, Add Comment form, readOnly mode
CourseCommentsPanel.test.tsx 11 Same as above for course-level; inline resolveComment/deleteComment API calls
LifecycleSection.test.tsx 8 Loading, 404 vs other errors, effectiveState logic, cache invalidation on hasChanges
LifecycleModal.test.tsx 7 Open/close, loading, null state, effectiveState, onClose callback
CourseLifecycleSection.test.tsx 17 Loading, error, aggregate badge, block count breakdown, all 4 action buttons

@eemaanamir eemaanamir force-pushed the eemaan/cms-lifecycle branch 2 times, most recently from 4601eb6 to 3be459e Compare April 7, 2026 21:08
…idebar files

- Create LifecycleModal: wraps LifecycleBadge + LifecycleActionButtons +
  BlockCommentsPanel in a Paragon ModalDialog (no sidebar dependency)
- Update CardHeader: add lifecycleState + onClickLifecycle props; render
  clickable LifecycleBadge next to status badges in each card header
- Update SectionCard / SubsectionCard / UnitCard: open LifecycleModal on
  badge click; modal is only rendered when blockLifecycleState is available
- Add LifecycleSection to old unit-page PublishControls sidebar (no new deps)
- Add CourseLifecycleSection to OutlineSidebar for course-level review state
- Delete backported info-sidebar files (SectionInfoSidebar, SubsectionInfoSidebar,
  UnitInfoSidebar, CourseInfoSidebar) and backported unit-sidebar files —
  the modal approach has zero dependency on the new sidebar system

fix: improve course review status sidebar styling and unit section margins

- CourseLifecycleSection: add labeled sub-sections (Current Status, Block
  Breakdown, Actions) with uppercase muted headings for better scannability
- OutlineSidebar: rename heading to 'Course Review Status'
- PublishControls: wrap LifecycleSection in px-3 to match sidebar side margins

fix: hide standard publish button when lifecycle system is active on unit page

When a unit is enrolled in the review workflow (useBlockState returns data),
pass hidePublishButton=true to SidebarFooter so the native Publish button is
suppressed — publishing is handled exclusively through LifecycleSection.

fix: invalidate all lifecycle queries on course-level mutations

Course-level mutations were only invalidating lifecycleQueryKeys.courseState,
leaving every block's cached state stale. Switching to queryKey: ['lifecycle']
matches what block-level mutations already do, so all block badges in card
headers re-fetch immediately after Submit All / Approve All / Publish Course.

fix: refresh Redux outline state after lifecycle publish

Lifecycle publish mutations only invalidated React Query caches, but the
course outline's section/subsection/unit published state (yellow draft
badges, hasChanges) lives in Redux and was not updated without a reload.

- LifecycleModal: accept onPublishSuccess prop and call it alongside onClose
- SectionCard / SubsectionCard / UnitCard: pass onPublishSuccess that
  dispatches fetchCourseSectionQuery([section.id]) to pull fresh Redux state
- CourseLifecycleSection: dispatch fetchCourseOutlineIndexQuery after Publish
  Course to refresh the entire outline tree in Redux
- usePublishCourse: accept options.onSuccess callback (mirrors usePublishBlock)

fix: use reactive useEffect to refresh Redux outline after lifecycle publish

The previous approach of threading an onPublishSuccess callback through
LifecycleModal → LifecycleActionButtons → usePublishBlock was unreliable
because TanStack Query captures useMutation options at hook-init time,
making the callback stale after component re-renders.

New approach: watch blockLifecycleState?.state (and data?.aggregateState
for course-level) in a useEffect. When a block/course transitions from any
non-published state to 'published', dispatch the Redux thunk directly to
refresh the outline without depending on any mutation callback chain.

- SectionCard / SubsectionCard / UnitCard: useEffect dispatches
  fetchCourseSectionQuery([section.id]) on state → 'published' transition
- CourseLifecycleSection: useEffect dispatches fetchCourseOutlineIndexQuery
  on aggregateState → 'published' transition
- Remove now-redundant onPublishSuccess props from LifecycleModal usages
- Revert usePublishCourse options param (no longer needed)
@eemaanamir eemaanamir force-pushed the eemaan/cms-lifecycle branch 2 times, most recently from 501992c to c160cf6 Compare April 8, 2026 20:34
@eemaanamir eemaanamir force-pushed the eemaan/cms-lifecycle branch from c160cf6 to a19e685 Compare April 8, 2026 21:15
@eemaanamir eemaanamir force-pushed the eemaan/cms-lifecycle branch from c6dcfbe to ecdc4df Compare April 8, 2026 22:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant