Skip to content

org dashboard design updates #2425

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 10 additions & 2 deletions frontends/api/src/mitxonline/clients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is kind of a side effect of some work I had in progress on this branch for adding the program enrollments API, just renames the enrollmentsApi to courseRunEnrollmentsApi, because that's what it is.

programsApi,
programCollectionsApi,
coursesApi,
Expand Down
13 changes: 7 additions & 6 deletions frontends/api/src/mitxonline/hooks/enrollment/index.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -12,7 +12,7 @@ const useCreateEnrollment = (opts: B2bApiB2bEnrollCreateRequest) => {
mutationFn: () => b2bApi.b2bEnrollCreate(opts),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: enrollmentKeys.enrollmentsList(),
queryKey: enrollmentKeys.courseRunEnrollmentsList(),
})
},
})
Expand All @@ -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(),
})
},
})
Expand All @@ -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(),
})
},
})
Expand Down
19 changes: 14 additions & 5 deletions frontends/api/src/mitxonline/hooks/enrollment/queries.ts
Original file line number Diff line number Diff line change
@@ -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<CourseRunEnrollment[]> => {
return enrollmentsApi.enrollmentsList().then((res) => res.data)
return courseRunEnrollmentsApi.enrollmentsList().then((res) => res.data)
},
}),
}
Expand Down
1 change: 1 addition & 0 deletions frontends/main/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand All @@ -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}`,
Expand Down Expand Up @@ -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")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -398,7 +398,7 @@ const DashboardCard: React.FC<DashboardCardProps> = ({
)
const contextMenu = isLoading ? (
<Skeleton variant="rectangular" width={12} height={24} />
) : menuItems.length > 0 ? (
) : (
<SimpleMenu
items={menuItems}
trigger={
Expand All @@ -407,12 +407,13 @@ const DashboardCard: React.FC<DashboardCardProps> = ({
variant="text"
aria-label="More options"
status={enrollment?.status}
hidden={menuItems.length === 0}
>
<RiMore2Line />
</MenuButton>
}
/>
) : null
)
const desktopLayout = (
<CardRoot
screenSize="desktop"
Expand Down Expand Up @@ -455,7 +456,7 @@ const DashboardCard: React.FC<DashboardCardProps> = ({
<Stack
direction="row"
alignItems="center"
justifyContent="space-between"
justifyContent="end"
width="100%"
>
{startDateSection}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ const EnrollmentExpandCollapse: React.FC<EnrollmentExpandCollapseProps> = ({

const EnrollmentDisplay = () => {
const { data: enrolledCourses, isLoading } = useQuery({
...enrollmentQueries.enrollmentsList(),
...enrollmentQueries.courseRunEnrollmentsList(),
select: mitxonlineEnrollments,
throwOnError: (error) => {
const err = error as MaybeHasStatusAndDetail
Expand Down
36 changes: 27 additions & 9 deletions frontends/main/src/app-pages/DashboardPage/OrganizationContent.tsx
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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 (
<ProgramRoot data-testid="org-program-collection-root">
<ProgramHeader>
<Typography variant="h5" component="h2">
{collection.title}
</Typography>
<ProgramDescription
variant="body2"
dangerouslySetInnerHTML={{ __html: sanitizedDescription }}
/>
</ProgramHeader>
<PlainList>
{collection.programIds.map((programId) => (
Expand All @@ -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 }),
)
Expand All @@ -135,16 +147,20 @@ 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 (
<ProgramRoot data-testid="org-program-root">
<ProgramHeader>
<Typography variant="h5" component="h2">
{program.title}
</Typography>
<Typography variant="body1">{program.description}</Typography>
<ProgramDescription
variant="body2"
dangerouslySetInnerHTML={{ __html: sanitizedHtml }}
/>
</ProgramHeader>
<PlainList>
{transform
Expand Down Expand Up @@ -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 ?? ""}
Expand All @@ -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({}),
Expand Down Expand Up @@ -270,7 +288,7 @@ const OrganizationContentInternal: React.FC<
<OrgProgramDisplay
key={program.key}
program={program}
enrollments={enrollments.data}
courseRunEnrollments={courseRunEnrollments.data}
programLoading={programs.isLoading}
orgId={orgId}
/>
Expand All @@ -286,7 +304,7 @@ const OrganizationContentInternal: React.FC<
<OrgProgramCollectionDisplay
key={collection.title}
collection={transformedCollection}
enrollments={enrollments.data}
enrollments={courseRunEnrollments.data}
orgId={orgId}
/>
)
Expand Down
20 changes: 20 additions & 0 deletions yarn.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading