From 6f98176bf0117bb1b63d3adcba32ae682871f108 Mon Sep 17 00:00:00 2001 From: Carey Gumaer Date: Wed, 19 Nov 2025 16:57:50 -0500 Subject: [PATCH 01/26] basic skeleton of loading program enrollments from v2 endpoint --- .../mitxonline/hooks/enrollment/queries.ts | 6 +- .../CoursewareDisplay/DashboardCard.tsx | 340 ++++++++++-------- .../CoursewareDisplay/EnrollmentDisplay.tsx | 84 +++-- .../CoursewareDisplay/transform.ts | 19 + 4 files changed, 269 insertions(+), 180 deletions(-) diff --git a/frontends/api/src/mitxonline/hooks/enrollment/queries.ts b/frontends/api/src/mitxonline/hooks/enrollment/queries.ts index 67f0a9855b..2110e1206e 100644 --- a/frontends/api/src/mitxonline/hooks/enrollment/queries.ts +++ b/frontends/api/src/mitxonline/hooks/enrollment/queries.ts @@ -1,7 +1,7 @@ import { queryOptions } from "@tanstack/react-query" import type { CourseRunEnrollmentRequestV2, - UserProgramEnrollmentDetail, + V2UserProgramEnrollmentDetail, } from "@mitodl/mitxonline-api-axios/v2" import { courseRunEnrollmentsApi, programEnrollmentsApi } from "../../clients" @@ -35,9 +35,9 @@ const enrollmentQueries = { programEnrollmentsList: (opts?: RawAxiosRequestConfig) => queryOptions({ queryKey: enrollmentKeys.programEnrollmentsList(opts), - queryFn: async (): Promise => { + queryFn: async (): Promise => { return programEnrollmentsApi - .programEnrollmentsList(opts) + .v2ProgramEnrollmentsList(opts) .then((res) => res.data) }, }), diff --git a/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.tsx b/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.tsx index 1d0787cb60..ce6a971d95 100644 --- a/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.tsx +++ b/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.tsx @@ -9,10 +9,16 @@ import { LoadingSpinner, } from "ol-components" import NextLink from "next/link" -import { EnrollmentStatus, EnrollmentMode } from "./types" +import { + EnrollmentStatus, + EnrollmentMode, + DashboardResourceType, +} from "./types" import type { DashboardResource, DashboardCourse, + DashboardProgram, + DashboardProgramCollection, DashboardCourseEnrollment, } from "./types" import { ActionButton, Button, ButtonLink } from "@mitodl/smoot-design" @@ -35,6 +41,25 @@ import { useCreateEnrollment } from "api/mitxonline-hooks/enrollment" import { mitxUserQueries } from "api/mitxonline-hooks/user" import { useQuery } from "@tanstack/react-query" +// Type guard functions +const isDashboardCourse = ( + resource: DashboardResource, +): resource is DashboardCourse => { + return resource.type === DashboardResourceType.Course +} + +const isDashboardProgram = ( + resource: DashboardResource, +): resource is DashboardProgram => { + return resource.type === DashboardResourceType.Program +} + +const isDashboardProgramCollection = ( + resource: DashboardResource, +): resource is DashboardProgramCollection => { + return resource.type === DashboardResourceType.ProgramCollection +} + const CardRoot = styled.div<{ screenSize: "desktop" | "mobile" }>(({ theme, screenSize }) => [ @@ -385,163 +410,182 @@ const DashboardCard: React.FC = ({ buttonHref, titleAction, }) => { - const course = dashboardResource as DashboardCourse - const { title, marketingUrl, enrollment, run } = course const oneClickEnroll = useOneClickEnroll() - const coursewareUrl = run.coursewareUrl - const titleHref = - titleAction === "marketing" ? marketingUrl : (coursewareUrl ?? marketingUrl) - const hasEnrolled = - enrollment?.status && enrollment.status !== EnrollmentStatus.NotEnrolled - const titleClick: React.MouseEventHandler | undefined = - titleAction === "courseware" && coursewareUrl && !hasEnrolled - ? (e) => { - e.preventDefault() - if (!course.coursewareId) return - oneClickEnroll.mutate({ - href: coursewareUrl, - coursewareId: course.coursewareId, - }) - } - : undefined + // Type-specific logic + if (isDashboardCourse(dashboardResource)) { + const course = dashboardResource + const { title, marketingUrl, enrollment, run } = course - const titleSection = isLoading ? ( - <> - - - - - ) : ( - <> - - {title} - - {enrollment?.status === EnrollmentStatus.Completed && - run.certificate?.link ? ( - - {} - View Certificate - - ) : null} - {enrollment?.mode !== EnrollmentMode.Verified && offerUpgrade ? ( - { + e.preventDefault() + if (!course.coursewareId) return + oneClickEnroll.mutate({ + href: coursewareUrl, + coursewareId: course.coursewareId, + }) + } + : undefined + + const titleSection = isLoading ? ( + <> + + + + + ) : ( + <> + + {title} + + {enrollment?.status === EnrollmentStatus.Completed && + run.certificate?.link ? ( + + {} + View Certificate + + ) : null} + {enrollment?.mode !== EnrollmentMode.Verified && offerUpgrade ? ( + + ) : null} + + ) + const buttonSection = isLoading ? ( + + ) : ( + <> + - ) : null} - - ) - const buttonSection = isLoading ? ( - - ) : ( - <> - - + + ) + const startDateSection = isLoading ? ( + + ) : run.startDate ? ( + + ) : null + const menuItems = contextMenuItems.concat( + enrollment?.id ? getDefaultContextMenuItems(title, enrollment) : [], + ) + const contextMenu = isLoading ? ( + + ) : ( +