diff --git a/frontends/api/src/mitxonline/clients.ts b/frontends/api/src/mitxonline/clients.ts index 8d93b2e2f8..073ff1d62e 100644 --- a/frontends/api/src/mitxonline/clients.ts +++ b/frontends/api/src/mitxonline/clients.ts @@ -24,29 +24,37 @@ const BASE_PATH = const usersApi = new UsersApi(undefined, BASE_PATH, axiosInstance) const b2bApi = new B2bApi(undefined, BASE_PATH, axiosInstance) -const enrollmentsApi = new EnrollmentsApi(undefined, BASE_PATH, axiosInstance) const programsApi = new ProgramsApi(undefined, BASE_PATH, axiosInstance) const programCollectionsApi = new ProgramCollectionsApi( undefined, BASE_PATH, axiosInstance, ) + const programCertificatesApi = new ProgramCertificatesApi( undefined, BASE_PATH, axiosInstance, ) + const coursesApi = new CoursesApi(undefined, BASE_PATH, axiosInstance) + const courseCertificatesApi = new CourseCertificatesApi( undefined, BASE_PATH, axiosInstance, ) +const courseRunEnrollmentsApi = new EnrollmentsApi( + undefined, + BASE_PATH, + axiosInstance, +) + export { usersApi, b2bApi, - enrollmentsApi, + courseRunEnrollmentsApi, programsApi, programCollectionsApi, coursesApi, diff --git a/frontends/api/src/mitxonline/hooks/enrollment/index.ts b/frontends/api/src/mitxonline/hooks/enrollment/index.ts index 6c40a192b2..7196d7b869 100644 --- a/frontends/api/src/mitxonline/hooks/enrollment/index.ts +++ b/frontends/api/src/mitxonline/hooks/enrollment/index.ts @@ -1,6 +1,6 @@ import { enrollmentQueries, enrollmentKeys } from "./queries" import { useMutation, useQueryClient } from "@tanstack/react-query" -import { b2bApi, enrollmentsApi } from "../../clients" +import { b2bApi, courseRunEnrollmentsApi } from "../../clients" import { B2bApiB2bEnrollCreateRequest, EnrollmentsApiEnrollmentsPartialUpdateRequest, @@ -12,7 +12,7 @@ const useCreateEnrollment = (opts: B2bApiB2bEnrollCreateRequest) => { mutationFn: () => b2bApi.b2bEnrollCreate(opts), onSuccess: () => { queryClient.invalidateQueries({ - queryKey: enrollmentKeys.enrollmentsList(), + queryKey: enrollmentKeys.courseRunEnrollmentsList(), }) }, }) @@ -23,10 +23,10 @@ const useUpdateEnrollment = ( ) => { const queryClient = useQueryClient() return useMutation({ - mutationFn: () => enrollmentsApi.enrollmentsPartialUpdate(opts), + mutationFn: () => courseRunEnrollmentsApi.enrollmentsPartialUpdate(opts), onSuccess: () => { queryClient.invalidateQueries({ - queryKey: enrollmentKeys.enrollmentsList(), + queryKey: enrollmentKeys.courseRunEnrollmentsList(), }) }, }) @@ -35,10 +35,11 @@ const useUpdateEnrollment = ( const useDestroyEnrollment = (enrollmentId: number) => { const queryClient = useQueryClient() return useMutation({ - mutationFn: () => enrollmentsApi.enrollmentsDestroy({ id: enrollmentId }), + mutationFn: () => + courseRunEnrollmentsApi.enrollmentsDestroy({ id: enrollmentId }), onSuccess: () => { queryClient.invalidateQueries({ - queryKey: enrollmentKeys.enrollmentsList(), + queryKey: enrollmentKeys.courseRunEnrollmentsList(), }) }, }) diff --git a/frontends/api/src/mitxonline/hooks/enrollment/queries.ts b/frontends/api/src/mitxonline/hooks/enrollment/queries.ts index 64fa9a7f86..1d790f3dbf 100644 --- a/frontends/api/src/mitxonline/hooks/enrollment/queries.ts +++ b/frontends/api/src/mitxonline/hooks/enrollment/queries.ts @@ -1,19 +1,28 @@ import { queryOptions } from "@tanstack/react-query" import type { CourseRunEnrollment } from "@mitodl/mitxonline-api-axios/v2" -import { enrollmentsApi } from "../../clients" +import { courseRunEnrollmentsApi } from "../../clients" const enrollmentKeys = { root: ["mitxonline", "enrollments"], - enrollmentsList: () => [...enrollmentKeys.root, "programEnrollments", "list"], + courseRunEnrollmentsList: () => [ + ...enrollmentKeys.root, + "courseRunEnrollments", + "list", + ], + programEnrollmentsList: () => [ + ...enrollmentKeys.root, + "programEnrollments", + "list", + ], } const enrollmentQueries = { - enrollmentsList: () => + courseRunEnrollmentsList: () => queryOptions({ - queryKey: enrollmentKeys.enrollmentsList(), + queryKey: enrollmentKeys.courseRunEnrollmentsList(), queryFn: async (): Promise => { - return enrollmentsApi.enrollmentsList().then((res) => res.data) + return courseRunEnrollmentsApi.enrollmentsList().then((res) => res.data) }, }), } diff --git a/frontends/main/package.json b/frontends/main/package.json index b7e5e2a999..6c0733ab08 100644 --- a/frontends/main/package.json +++ b/frontends/main/package.json @@ -22,6 +22,7 @@ "@tanstack/react-query": "^5.66", "api": "workspace:*", "classnames": "^2.5.1", + "dompurify": "^3.2.6", "formik": "^2.4.6", "iso-639-1": "^3.1.4", "lodash": "^4.17.21", diff --git a/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.test.tsx b/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.test.tsx index 9e6789aaa8..59e167555a 100644 --- a/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.test.tsx +++ b/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.test.tsx @@ -147,7 +147,7 @@ describe.each([ course.enrollment?.status === EnrollmentStatus.NotEnrolled || !course.enrollment ) { - expect(coursewareCTA).toHaveTextContent("Enroll") + expect(coursewareCTA).toHaveTextContent("Start Course") } else { expect(coursewareCTA).toHaveTextContent( `${expected.labelPrefix} Course`, @@ -163,7 +163,7 @@ describe.each([ course.enrollment?.status === EnrollmentStatus.NotEnrolled || !course.enrollment ) { - expect(coursewareCTA).toHaveTextContent("Enroll") + expect(coursewareCTA).toHaveTextContent(`Start ${courseNoun}`) } else { expect(coursewareCTA).toHaveTextContent( `${expected.labelPrefix} ${courseNoun}`, @@ -455,7 +455,7 @@ describe.each([ status === undefined || !course.enrollment ) { - expect(coursewareButton).toHaveTextContent("Enroll") + expect(coursewareButton).toHaveTextContent("Start Course") } else { expect(coursewareButton).toHaveTextContent("Continue Course") } diff --git a/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.tsx b/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.tsx index 2735425af8..e697569c04 100644 --- a/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.tsx +++ b/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.tsx @@ -132,7 +132,7 @@ const getCoursewareText = ({ courseNoun: string }) => { if (!enrollmentStatus || enrollmentStatus === EnrollmentStatus.NotEnrolled) { - return "Enroll" + return `Start ${courseNoun}` } if (!endDate) return `Continue ${courseNoun}` if (isInPast(endDate)) { @@ -398,7 +398,7 @@ const DashboardCard: React.FC = ({ ) const contextMenu = isLoading ? ( - ) : menuItems.length > 0 ? ( + ) : ( = ({ variant="text" aria-label="More options" status={enrollment?.status} + hidden={menuItems.length === 0} > } /> - ) : null + ) const desktopLayout = ( = ({ {startDateSection} diff --git a/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/EnrollmentDisplay.tsx b/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/EnrollmentDisplay.tsx index bbc052bd7a..3b206908a7 100644 --- a/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/EnrollmentDisplay.tsx +++ b/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/EnrollmentDisplay.tsx @@ -175,7 +175,7 @@ const EnrollmentExpandCollapse: React.FC = ({ const EnrollmentDisplay = () => { const { data: enrolledCourses, isLoading } = useQuery({ - ...enrollmentQueries.enrollmentsList(), + ...enrollmentQueries.courseRunEnrollmentsList(), select: mitxonlineEnrollments, throwOnError: (error) => { const err = error as MaybeHasStatusAndDetail diff --git a/frontends/main/src/app-pages/DashboardPage/OrganizationContent.tsx b/frontends/main/src/app-pages/DashboardPage/OrganizationContent.tsx index b716ce470f..1a7c49f45c 100644 --- a/frontends/main/src/app-pages/DashboardPage/OrganizationContent.tsx +++ b/frontends/main/src/app-pages/DashboardPage/OrganizationContent.tsx @@ -1,6 +1,7 @@ "use client" import React from "react" +import DOMPurify from "dompurify" import Image from "next/image" import { useFeatureFlagEnabled } from "posthog-js/react" import { FeatureFlags } from "@/common/feature_flags" @@ -89,22 +90,33 @@ const ProgramHeader = styled.div(({ theme }) => ({ flexDirection: "column", gap: "16px", - backgroundColor: "rgba(243, 244, 248, 0.60)", // lightGray1 at 60% + backgroundColor: theme.custom.colors.white, borderRadius: "8px 8px 0px 0px", border: `1px solid ${theme.custom.colors.lightGray2}`, + borderBottom: `1px solid ${theme.custom.colors.red}`, })) +const ProgramDescription = styled(Typography)({ + p: { + margin: 0, + }, +}) const OrgProgramCollectionDisplay: React.FC<{ collection: DashboardProgramCollection enrollments?: CourseRunEnrollment[] orgId: number }> = ({ collection, enrollments, orgId }) => { + const sanitizedDescription = DOMPurify.sanitize(collection.description ?? "") return ( {collection.title} + {collection.programIds.map((programId) => ( @@ -122,10 +134,10 @@ const OrgProgramCollectionDisplay: React.FC<{ const OrgProgramDisplay: React.FC<{ program: DashboardProgram - enrollments?: CourseRunEnrollment[] + courseRunEnrollments?: CourseRunEnrollment[] programLoading: boolean orgId?: number -}> = ({ program, enrollments, programLoading, orgId }) => { +}> = ({ program, courseRunEnrollments, programLoading, orgId }) => { const courses = useQuery( coursesQueries.coursesList({ id: program.courseIds, org_id: orgId }), ) @@ -135,8 +147,9 @@ const OrgProgramDisplay: React.FC<{ if (programLoading || courses.isLoading) return skeleton const transformedCourses = transform.mitxonlineCourses({ courses: courses.data?.results ?? [], - enrollments: enrollments ?? [], + enrollments: courseRunEnrollments ?? [], }) + const sanitizedHtml = DOMPurify.sanitize(program.description) return ( @@ -144,7 +157,10 @@ const OrgProgramDisplay: React.FC<{ {program.title} - {program.description} + {transform @@ -217,7 +233,7 @@ const ProgramCard: React.FC<{ Component="li" key={program.key} dashboardResource={course} - courseNoun={"Course"} + courseNoun={"Module"} offerUpgrade={false} titleHref={course.run.coursewareUrl ?? ""} buttonHref={course.run.coursewareUrl ?? ""} @@ -241,7 +257,9 @@ const OrganizationContentInternal: React.FC< FeatureFlags.OrganizationDashboard, ) const orgId = org.id - const enrollments = useQuery(enrollmentQueries.enrollmentsList()) + const courseRunEnrollments = useQuery( + enrollmentQueries.courseRunEnrollmentsList(), + ) const programs = useQuery(programsQueries.programsList({ org_id: orgId })) const programCollections = useQuery( programCollectionQueries.programCollectionsList({}), @@ -270,7 +288,7 @@ const OrganizationContentInternal: React.FC< @@ -286,7 +304,7 @@ const OrganizationContentInternal: React.FC< ) diff --git a/yarn.lock b/yarn.lock index 3563ebeac0..cfedc860c3 100755 --- a/yarn.lock +++ b/yarn.lock @@ -6212,6 +6212,13 @@ __metadata: languageName: node linkType: hard +"@types/trusted-types@npm:^2.0.7": + version: 2.0.7 + resolution: "@types/trusted-types@npm:2.0.7" + checksum: 10/8e4202766a65877efcf5d5a41b7dd458480b36195e580a3b1085ad21e948bc417d55d6f8af1fd2a7ad008015d4117d5fdfe432731157da3c68678487174e4ba3 + languageName: node + linkType: hard + "@types/unist@npm:*, @types/unist@npm:^3.0.0": version: 3.0.3 resolution: "@types/unist@npm:3.0.3" @@ -9419,6 +9426,18 @@ __metadata: languageName: node linkType: hard +"dompurify@npm:^3.2.6": + version: 3.2.6 + resolution: "dompurify@npm:3.2.6" + dependencies: + "@types/trusted-types": "npm:^2.0.7" + dependenciesMeta: + "@types/trusted-types": + optional: true + checksum: 10/b91631ed0e4d17fae950ef53613cc009ed7e73adc43ac94a41dd52f35483f7538d13caebdafa7626e0da145fc8184e7ac7935f14f25b7e841b32fda777e40447 + languageName: node + linkType: hard + "domutils@npm:^2.5.2, domutils@npm:^2.8.0": version: 2.8.0 resolution: "domutils@npm:2.8.0" @@ -13949,6 +13968,7 @@ __metadata: "@types/slick-carousel": "npm:^1" api: "workspace:*" classnames: "npm:^2.5.1" + dompurify: "npm:^3.2.6" eslint: "npm:8.57.1" eslint-config-next: "npm:^14.2.7" formik: "npm:^2.4.6"