Skip to content

"program as course" design updates#3057

Open
gumaerc wants to merge 22 commits intomainfrom
cg/dashboard-crogram-display
Open

"program as course" design updates#3057
gumaerc wants to merge 22 commits intomainfrom
cg/dashboard-crogram-display

Conversation

@gumaerc
Copy link
Contributor

@gumaerc gumaerc commented Mar 17, 2026

What are the relevant tickets?

Closes https://github.com/mitodl/hq/issues/10521

Description (What does it do?)

This PR implements some design changes shown in the wireframes in the issue above. Support for displaying programs with display_mode: "course" was added, and some styles on DashboardCard were updated across the board based on the new designs. Several files were also renamed / restructured.

Screenshots (if appropriate):

image image

How can this be tested?

  • Ensure you have an instance of MITx Online spun up and configured to integrate with MIT Learn as described in the Readme
  • Your user will need lots of test data:
    • B2B contract with programs added to it that your user is a part of
    • Several B2C enrollments:
      • Individual course
      • Regular program
      • Program with display_mode: "course
    • Each of the B2C programs above should have several courses added to them, and at least one of them should:
      • Be completed with a certificate assigned
      • Be eligible for certificate upgrade
  • Log in and view the dashboard
  • Carefully go over each section and smoke test every part of DashboardCard in any conceivable scenario, checking what's rendered against the Figma wireframes in the issue above

Copilot AI review requested due to automatic review settings March 17, 2026 00:04
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the MITx Online dashboard/product UI to support “program as course” presentation and splits the previous combined enrollment display into more focused components, including new program-specific card and progress badge UI.

Changes:

  • Replace the old EnrollmentDisplay wrapper with AllEnrollmentsDisplay (home) and ProgramEnrollmentDisplay (program page).
  • Add new “program as course” UI via ProgramAsCourseCard and ProgressBadge, and route program enrollment cards based on display_mode.
  • Refactor helpers/tests to distinguish course-run vs program enrollment status.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
frontends/main/src/app-pages/ProductPages/MitxOnlineCourseCard.tsx Removes now-unneeded comment around image fallback behavior.
frontends/main/src/app-pages/DashboardPage/ProgramContent.tsx Switches program dashboard content to ProgramEnrollmentDisplay.
frontends/main/src/app-pages/DashboardPage/ProgramContent.test.tsx Updates mocks/assertions for ProgramEnrollmentDisplay.
frontends/main/src/app-pages/DashboardPage/HomeContent.tsx Uses AllEnrollmentsDisplay for the “My Learning” section.
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/helpers.ts Renames course-run status helper and adds program enrollment status helper.
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/helpers.test.tsx Updates tests for renamed course-run status helper.
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/ProgressBadge.tsx Adds a new badge component for enrollment/progress state.
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/ProgramEnrollmentDisplay.tsx Extracts program requirements/enrollment UI into a dedicated component.
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/ProgramAsCourseCard.tsx Adds the “program as course” card UI (modules list + dates popover).
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/EnrollmentDisplay.tsx Removes the old combined component implementation.
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardDialogs.test.tsx Updates dashboard dialog tests to render AllEnrollmentsDisplay.
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.tsx Refactors card layout; routes program cards to ProgramAsCourseCard when display_mode is “Course”; updates CTA label logic.
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.test.tsx Updates CTA text expectations after noun removal.
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/AllEnrollmentsDisplay.tsx Introduces standalone “My Learning” (all enrollments) display component.
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/AllEnrollmentsDisplay.test.tsx Updates tests to use AllEnrollmentsDisplay + ProgramEnrollmentDisplay directly.
frontends/main/src/app-pages/DashboardPage/ContractContent.tsx Removes now-deprecated noun="Module" usage on DashboardCard.


// Build sections from requirement tree
const requirementSections =
program?.req_tree
{} as Record<number, typeof rawEnrollments>,
)

const firstRequirementSection = program?.req_tree.find(
Comment on lines +344 to +349
<LinkButton
onClick={(event) => setPopoverAnchorEl(event.currentTarget)}
title=""
tooltip=""
aria-label=""
>
Comment on lines +305 to +358
{startDatePopoverString && endDatePopoverString && daysTillEnd && (
<>
<HorizontalSeparator />
<Popover
anchorEl={popoverAnchorEl}
open={!!popoverAnchorEl}
onClose={() => setPopoverAnchorEl(null)}
>
<DatePopoverContent>
<Stack direction="column" gap="4px">
<Typography
variant="subtitle3"
color={theme.custom.colors.black}
>
Important Dates:
</Typography>
<Typography
variant="body3"
color={theme.custom.colors.black}
>
This course{" "}
<Typography variant="subtitle3" component="span">
started
</Typography>{" "}
on {startDatePopoverString}
</Typography>
</Stack>
<Typography
variant="body3"
color={theme.custom.colors.black}
>
This course will{" "}
<Typography variant="subtitle3" component="span">
end
</Typography>{" "}
in {daysTillEnd} days on {endDatePopoverString}
</Typography>
</DatePopoverContent>
</Popover>
<LinkButton
onClick={(event) => setPopoverAnchorEl(event.currentTarget)}
title=""
tooltip=""
aria-label=""
>
<Typography
variant="body2"
color={theme.custom.colors.silverGrayDark}
>
Ends {linkButtonEndDate}
</Typography>
</LinkButton>
</>
)}
Comment on lines +102 to +119
const getProgramEnrollmentStatus = (
programEnrollment: V3UserProgramEnrollment | undefined,
enrolledCourseCount: number,
): EnrollmentStatus => {
if (!programEnrollment) {
return EnrollmentStatus.NotEnrolled
}

if (programEnrollment.certificate) {
return EnrollmentStatus.Completed
}

if (enrolledCourseCount > 0) {
return EnrollmentStatus.Enrolled
}

return EnrollmentStatus.NotEnrolled
}
Comment on lines +925 to +934
const isCourseDisplayMode =
resource.data.program.display_mode === DisplayModeEnum.Course

if (isCourseDisplayMode) {
return (
<ProgramAsCourseCard
programId={resource.data.program.id}
Component={Component}
className={className}
variant={variant}
/>
Comment on lines +30 to +44
const extractCoursesFromNode = (node: V2ProgramRequirement): number[] => {
const courses: number[] = []

if (node.data.node_type === "course" && node.data.course) {
courses.push(node.data.course)
}

if (node.children) {
node.children.forEach((child) => {
courses.push(...extractCoursesFromNode(child))
})
}

return courses
}
Comment on lines +33 to +40
const ProgressBadge: React.FC<ProgressBadgeProps> = ({ enrollmentStatus }) => {
const label =
enrollmentStatus === EnrollmentStatus.Completed
? "Completed"
: enrollmentStatus === EnrollmentStatus.Enrolled
? "In Progress"
: "Not Started"

@gumaerc gumaerc force-pushed the cg/dashboard-crogram-display branch from eef7e2e to 4e18a95 Compare March 18, 2026 17:03
Comment on lines +224 to +226
if (!enrolledInProgram) {
return <NotFoundPage />
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The component renders a NotFoundPage on API failure because it doesn't handle the isError state from the programEnrollments query, treating an error as a 'not enrolled' case.
Severity: MEDIUM

Suggested Fix

Check the isError status from the useQuery hook for programEnrollments. If isError is true, render an appropriate error message or a null state instead of the NotFoundPage. This will distinguish between a failed API request and a user who is genuinely not enrolled.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location:
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/ProgramEnrollmentDisplay.tsx#L224-L226

Potential issue: In the `ProgramEnrollmentDisplay` component, if the API call to fetch
`programEnrollments` fails, the `useQuery` hook returns `undefined` data. Due to
optional chaining, the `enrolledInProgram` variable also becomes `undefined`. The
component logic only checks for the `isLoading` state and not the `isError` state.
Consequently, after loading completes, the condition `!enrolledInProgram` evaluates to
true, causing the component to render a `NotFoundPage`. This misleads the user by
presenting a 'not found' error instead of indicating a temporary API or network issue.

@gumaerc gumaerc force-pushed the cg/dashboard-crogram-display branch from 4e18a95 to 0a25023 Compare March 18, 2026 20:09
@mitodl mitodl deleted a comment from Copilot AI Mar 18, 2026
@gumaerc gumaerc requested a review from Copilot March 18, 2026 22:49
@gumaerc gumaerc changed the title [WIP] "program as course" design updates "program as course" design updates Mar 18, 2026
@gumaerc gumaerc added Needs Review An open Pull Request that is ready for review and removed Work in Progress labels Mar 18, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the MITx Online dashboard “program as course” experience by splitting the old EnrollmentDisplay into focused components, adding new UI for program-as-course cards, and refactoring dashboard cards/CTAs to support the new designs.

Changes:

  • Replace the monolithic EnrollmentDisplay with AllEnrollmentsDisplay (dashboard home) and ProgramEnrollmentDisplay (program detail).
  • Add “program as course” UI pieces (ProgramAsCourseCard, ProgressBadge) and extend helpers for program enrollment status.
  • Refactor DashboardCard CTA logic/styling and update/replace related tests.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
frontends/main/src/app-pages/ProductPages/MitxOnlineCourseCard.tsx Removes a now-unneeded comment around feature image fallback behavior.
frontends/main/src/app-pages/DashboardPage/ProgramContent.tsx Switches program dashboard content to use ProgramEnrollmentDisplay.
frontends/main/src/app-pages/DashboardPage/ProgramContent.test.tsx Updates component mocking/tests to reflect ProgramEnrollmentDisplay.
frontends/main/src/app-pages/DashboardPage/HomeContent.tsx Uses AllEnrollmentsDisplay for “My Learning” on the home dashboard.
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/helpers.ts Renames course-run status helper and adds program enrollment status helper.
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/helpers.test.tsx Updates tests for renamed course-run enrollment status helper.
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/ProgressBadge.tsx Adds a new badge component for “Not Started / In Progress / Completed”.
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/ProgramEnrollmentDisplay.tsx Introduces program requirement tree display (courses + program-as-course items).
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/ProgramEnrollmentDisplay.test.tsx Adds test coverage for ProgramEnrollmentDisplay behaviors and ordering.
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/ProgramAsCourseCard.tsx Adds a stacked “program as course” card with progress + date popover.
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/ProgramAsCourseCard.test.tsx Adds tests for program-as-course card UI and date popover logic.
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/EnrollmentStatusIndicator.tsx Deletes the old status indicator component (replaced by new UI).
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/EnrollmentDisplay.tsx Deletes the old combined enrollment display component.
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/EnrollmentDisplay.test.tsx Removes tests for the deleted EnrollmentDisplay.
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardDialogs.test.tsx Updates dialog tests to render AllEnrollmentsDisplay instead of removed component.
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.tsx Refactors card layout and CTA rendering; adds program-as-course branching.
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.test.tsx Updates CTA expectations and adds tests for CTA style mapping helper.
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/AllEnrollmentsDisplay.tsx New component implementing the old “My Learning” list behavior.
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/AllEnrollmentsDisplay.test.tsx Adds test coverage for AllEnrollmentsDisplay.
frontends/main/src/app-pages/DashboardPage/ContractContent.tsx Removes deprecated noun prop usage from DashboardCard calls.
frontends/main/src/app-pages/DashboardPage/ContractContent.test.tsx Removes assertions tied to deleted enrollment status indicator UI.

Comment on lines +306 to +309
const modules = (programCourses?.results || []).filter((course) =>
moduleIds.includes(course.id),
)

Comment on lines +332 to +335

const programEnrollmentStatus = getProgramEnrollmentStatus(
programEnrollment,
enrolledCount,
Comment on lines +423 to +425
title=""
tooltip=""
aria-label=""
return EnrollmentStatus.Enrolled
}

return EnrollmentStatus.NotEnrolled
Comment on lines 431 to 472
@@ -412,15 +436,13 @@ const CoursewareButton = styled(
href,
disabled,
className,
noun,
isProgram,
onClick,
isPending,
...others
}: CoursewareButtonProps) => {
const coursewareText = getCoursewareTextAndIcon({
const coursewareText = getCoursewareButtonStyle({
endDate,
noun,
enrollmentStatus,
isProgram,
})
@@ -430,16 +452,15 @@ const CoursewareButton = styled(
// Programs or enrolled courses with started runs: show link
if ((isProgram || hasEnrolled) && (hasStarted || !startDate) && href) {
return (
<ButtonLink
<StyledCoursewareButtonLink
size="small"
variant="primary"
endIcon={coursewareText.endIcon}
variant={coursewareText.variant}
href={href}
className={className}
{...others}
>
{coursewareText.text}
</ButtonLink>
</StyledCoursewareButtonLink>
)
}

@@ -451,26 +472,22 @@ const CoursewareButton = styled(
)
EnrollmentStatus.Completed
)
} else {
return false
You have completed
<Typography component="span" variant="subtitle2">
{" "}
{completedCount} of {totalCount} courses{" "}
Comment on lines +246 to +259
const sectionCompletedCount = section.items.filter((item) => {
if (item.resourceType === "course") {
const bestEnrollment = selectBestEnrollment(
item.data,
enrollmentsByCourseId[item.data.id] || [],
)
return (
getCourseRunEnrollmentStatus(bestEnrollment) ===
EnrollmentStatus.Completed
)
} else {
return false
}
}).length
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Needs Review An open Pull Request that is ready for review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants