From d32f692ce17148671688a59922212cb75dc987ae Mon Sep 17 00:00:00 2001 From: Chris Chudzicki Date: Mon, 6 Oct 2025 11:45:53 -0400 Subject: [PATCH 1/5] simplify nested ternaries to if/else --- .../CoursewareDisplay/DashboardCard.tsx | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.tsx b/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.tsx index 32da8e1df9..013bc7b82d 100644 --- a/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.tsx +++ b/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.tsx @@ -169,19 +169,9 @@ const CoursewareButton = styled( const userCountry = mitxOnlineUser.data?.legal_address?.country const userYearOfBirth = mitxOnlineUser.data?.user_profile?.year_of_birth const showJustInTimeDialog = !userCountry || !userYearOfBirth - return (hasStarted && href) || !hasEnrolled ? ( - hasEnrolled && href ? ( - } - href={href} - className={className} - {...others} - > - {coursewareText} - - ) : ( + + if (!hasEnrolled /* Trigger signup */) { + return ( ) } else if (hasStarted && href /* Link to course */) { @@ -342,6 +357,7 @@ const CourseStartCountdown: React.FC<{ type DashboardCardProps = { Component?: React.ElementType + titleAction: "marketing" | "courseware" dashboardResource: DashboardResource showNotComplete?: boolean className?: string @@ -349,7 +365,6 @@ type DashboardCardProps = { offerUpgrade?: boolean contextMenuItems?: SimpleMenuItem[] isLoading?: boolean - titleHref?: string | null buttonHref?: string | null } @@ -362,11 +377,29 @@ const DashboardCard: React.FC = ({ offerUpgrade = true, contextMenuItems = [], isLoading = false, - titleHref, 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 const titleSection = isLoading ? ( <> @@ -379,7 +412,8 @@ const DashboardCard: React.FC = ({ {title} From 85fa88b0d835a9e3dc003de3850f786ffb58ddc9 Mon Sep 17 00:00:00 2001 From: Chris Chudzicki Date: Mon, 6 Oct 2025 14:49:55 -0400 Subject: [PATCH 4/5] add cartesian product --- .../ol-test-utilities/src/cartesianProduct.ts | 56 +++++++++++++++++++ .../src/cartestianProduct.test.ts | 38 +++++++++++++ frontends/ol-test-utilities/src/index.ts | 1 + 3 files changed, 95 insertions(+) create mode 100644 frontends/ol-test-utilities/src/cartesianProduct.ts create mode 100644 frontends/ol-test-utilities/src/cartestianProduct.test.ts diff --git a/frontends/ol-test-utilities/src/cartesianProduct.ts b/frontends/ol-test-utilities/src/cartesianProduct.ts new file mode 100644 index 0000000000..b4b189117e --- /dev/null +++ b/frontends/ol-test-utilities/src/cartesianProduct.ts @@ -0,0 +1,56 @@ +// Provide several overloads for better type inference with up to 5 arrays. +function cartesianProduct(a: A[], b: B[]): (A & B)[] +function cartesianProduct(a: A[], b: B[], c: C[]): (A & B & C)[] +function cartesianProduct( + a: A[], + b: B[], + c: C[], + d: D[], +): (A & B & C & D)[] +function cartesianProduct( + a: A[], + b: B[], + c: C[], + d: D[], + e: E[], +): (A & B & C & D & E)[] +function cartesianProduct(...arrays: T[][]): T[] + +/** + * Generates the cartesian product of multiple arrays of objects, merging the + * objects. This can be used with jest.each for an effect similar to multiple + * pytest.mark.parametrize calls. + * + * For example: + * ```ts + * cartesianProduct( + * [{ x: 1 }, { x: 2 }], + * [{ y: 'a' }, { y: 'b' }], + * [{ z: 3 }, { z: 4 }] + * ) + * ``` + * + * would yield: + * ``` + * [ + * { x: 1, y: 'a', z: 3 }, + * { x: 1, y: 'a', z: 4 }, + * { x: 1, y: 'b', z: 3 }, + * { x: 1, y: 'b', z: 4 }, + * { x: 2, y: 'a', z: 3 }, + * { x: 2, y: 'a', z: 4 }, + * { x: 2, y: 'b', z: 3 }, + * { x: 2, y: 'b', z: 4 } + * ] + * ``` + */ +function cartesianProduct(...arrays: T[][]): T[] { + return arrays.reduce( + (acc, curr) => { + return acc.flatMap((a) => curr.map((b) => ({ ...a, ...b }))) + }, + [{}] as T[], + ) +} + +export default cartesianProduct diff --git a/frontends/ol-test-utilities/src/cartestianProduct.test.ts b/frontends/ol-test-utilities/src/cartestianProduct.test.ts new file mode 100644 index 0000000000..3ba05d97f8 --- /dev/null +++ b/frontends/ol-test-utilities/src/cartestianProduct.test.ts @@ -0,0 +1,38 @@ +import cartesianProduct from "./cartesianProduct" + +describe("cartesianProduct", () => { + it("should return an empty array when given empty arrays", () => { + const result = cartesianProduct([], []) + expect(result).toEqual([]) + }) + + it("should handle single array", () => { + const result = cartesianProduct([{ a: 1 }, { a: 2 }]) + expect(result).toEqual([{ a: 1 }, { a: 2 }]) + }) + + it("should generate cartesian product of two arrays", () => { + const result = cartesianProduct( + [ + { a: 0, x: 10 }, + { a: 1, x: 20 }, + ], + [{ y: "a" }, { y: "b" }, { y: "c" }], + [{ z: true }, { z: false }], + ) + expect(result).toEqual([ + { a: 0, x: 10, y: "a", z: true }, + { a: 0, x: 10, y: "a", z: false }, + { a: 0, x: 10, y: "b", z: true }, + { a: 0, x: 10, y: "b", z: false }, + { a: 0, x: 10, y: "c", z: true }, + { a: 0, x: 10, y: "c", z: false }, + { a: 1, x: 20, y: "a", z: true }, + { a: 1, x: 20, y: "a", z: false }, + { a: 1, x: 20, y: "b", z: true }, + { a: 1, x: 20, y: "b", z: false }, + { a: 1, x: 20, y: "c", z: true }, + { a: 1, x: 20, y: "c", z: false }, + ]) + }) +}) diff --git a/frontends/ol-test-utilities/src/index.ts b/frontends/ol-test-utilities/src/index.ts index 4b28cc5aea..0570104320 100644 --- a/frontends/ol-test-utilities/src/index.ts +++ b/frontends/ol-test-utilities/src/index.ts @@ -6,6 +6,7 @@ export * from "./assertions" export * from "./domQueries/byImageSrc" export * from "./domQueries/byTerm" export * from "./domQueries/forms" +export { default as cartesianProduct } from "./cartesianProduct" /** * This is moment-timezone. From 644df341f5e90b9e5cb3271bce5bf9085c622ad5 Mon Sep 17 00:00:00 2001 From: Chris Chudzicki Date: Mon, 6 Oct 2025 15:18:48 -0400 Subject: [PATCH 5/5] fix link, loaders --- .../CoursewareDisplay/DashboardCard.test.tsx | 234 +++++++++++++----- .../CoursewareDisplay/DashboardCard.tsx | 1 + .../DashboardDialogs.test.tsx | 28 ++- .../CoursewareDisplay/DashboardDialogs.tsx | 42 ++-- .../CoursewareDisplay/EnrollmentDisplay.tsx | 2 + .../DashboardPage/OrganizationContent.tsx | 4 +- 6 files changed, 208 insertions(+), 103 deletions(-) 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 d01b729fd8..1b8841fb19 100644 --- a/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.test.tsx +++ b/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.test.tsx @@ -7,16 +7,13 @@ import { within, } from "@/test-utils" import * as mitxonline from "api/mitxonline-test-utils" -import { - urls as testUrls, - factories as testFactories, - mockAxiosInstance, -} from "api/test-utils" +import { mockAxiosInstance } from "api/test-utils" import { DashboardCard, getDefaultContextMenuItems } from "./DashboardCard" import { dashboardCourse } from "./test-utils" import { faker } from "@faker-js/faker/locale/en" import moment from "moment" import { EnrollmentMode, EnrollmentStatus } from "./types" +import { cartesianProduct } from "ol-test-utilities" const pastDashboardCourse: typeof dashboardCourse = (...overrides) => { return dashboardCourse( @@ -52,23 +49,45 @@ const futureDashboardCourse: typeof dashboardCourse = (...overrides) => { ) } -beforeEach(() => { - // Mock user API call - const user = testFactories.user.user() +const mitxUser = mitxonline.factories.user.user + +const setupUserApis = () => { const mitxUser = mitxonline.factories.user.user() - setMockResponse.get(testUrls.userMe.get(), user) setMockResponse.get(mitxonline.urls.userMe.get(), mitxUser) -}) +} describe.each([ { display: "desktop", testId: "enrollment-card-desktop" }, { display: "mobile", testId: "enrollment-card-mobile" }, -])("EnrollmentCard $display", ({ testId }) => { +])("DashboardCard $display", ({ testId }) => { const getCard = () => screen.getByTestId(testId) - test("It shows course title with link to marketing url", () => { - const course = dashboardCourse() - renderWithProviders() + const originalLocation = window.location + + beforeAll(() => { + Object.defineProperty(window, "location", { + configurable: true, + enumerable: true, + value: { ...originalLocation, assign: jest.fn() }, + }) + }) + + afterAll(() => { + Object.defineProperty(window, "location", { + configurable: true, + enumerable: true, + value: originalLocation, + }) + }) + + test("It shows course title and links to marketingUrl if titleAction is marketing", async () => { + setupUserApis() + const course = dashboardCourse({ + marketingUrl: "?some-marketing-url", + }) + renderWithProviders( + , + ) const card = getCard() @@ -78,7 +97,23 @@ describe.each([ expect(courseLink).toHaveAttribute("href", course.marketingUrl) }) + test("It shows course title and links to courseware if titleAction is courseware", async () => { + setupUserApis() + const course = dashboardCourse() + renderWithProviders( + , + ) + + const card = getCard() + + const courseLink = within(card).getByRole("link", { + name: course.title, + }) + expect(courseLink).toHaveAttribute("href", course.run.coursewareUrl) + }) + test("Accepts a classname", () => { + setupUserApis() const course = dashboardCourse() const TheComponent = faker.helpers.arrayElement([ "li", @@ -88,6 +123,7 @@ describe.each([ ]) renderWithProviders( { - renderWithProviders() + setupUserApis() + renderWithProviders( + , + ) const card = getCard() const coursewareCTA = within(card).getByTestId("courseware-button") @@ -154,8 +193,9 @@ describe.each([ ])( "Courseware CTA shows correct label based on courseNoun prop and dates (case $case)", ({ course, expected }) => { + setupUserApis() const { view } = renderWithProviders( - , + , ) const card = getCard() const coursewareCTA = within(card).getByTestId("courseware-button") @@ -173,7 +213,11 @@ describe.each([ const courseNoun = faker.word.noun() view.rerender( - , + , ) if ( @@ -221,8 +265,11 @@ describe.each([ ])( "Shows upgrade banner based on run.canUpgrade and not already upgraded (canUpgrade: $overrides.canUpgrade)", ({ overrides, expectation }) => { + setupUserApis() const course = dashboardCourse(overrides) - renderWithProviders() + renderWithProviders( + , + ) const card = getCard() const upgradeRoot = within(card).queryByTestId("upgrade-root") @@ -236,6 +283,7 @@ describe.each([ ])( "Never shows upgrade banner if `offerUpgrade` is false", ({ offerUpgrade, expected }) => { + setupUserApis() const course = dashboardCourse({ run: { canUpgrade: true, @@ -247,6 +295,7 @@ describe.each([ renderWithProviders( , @@ -259,6 +308,7 @@ describe.each([ ) test("Upgrade banner shows correct price and deadline", () => { + setupUserApis() const certificateUpgradePrice = faker.commerce.price() const certificateUpgradeDeadline = moment() .startOf("day") @@ -275,7 +325,9 @@ describe.each([ enrollment: { mode: EnrollmentMode.Audit }, }) - renderWithProviders() + renderWithProviders( + , + ) const card = getCard() const upgradeRoot = within(card).getByTestId("upgrade-root") @@ -287,13 +339,16 @@ describe.each([ }) test("Shows number of days until course starts", () => { + setupUserApis() const startDate = moment() .startOf("day") .add(5, "days") .add(3, "hours") .toISOString() const enrollment = dashboardCourse({ run: { startDate } }) - renderWithProviders() + renderWithProviders( + , + ) const card = getCard() expect(card).toHaveTextContent(/starts in 5 days/i) @@ -302,6 +357,7 @@ describe.each([ test.each([{ showNotComplete: true }, { showNotComplete: false }])( "Shows incomplete status when showNotComplete is true", ({ showNotComplete }) => { + setupUserApis() const enrollment = faker.helpers.arrayElement([ { status: EnrollmentStatus.NotEnrolled }, { status: EnrollmentStatus.Enrolled }, @@ -313,6 +369,7 @@ describe.each([ } const { view } = renderWithProviders( , @@ -324,6 +381,7 @@ describe.each([ view.rerender( { + setupUserApis() renderWithProviders( , ) @@ -389,6 +449,7 @@ describe.each([ ])( "getDefaultContextMenuItems returns correct items", async ({ contextMenuItems }) => { + setupUserApis() const course = dashboardCourse() course.enrollment = { id: faker.number.int(), @@ -397,6 +458,7 @@ describe.each([ } renderWithProviders( , @@ -431,8 +493,10 @@ describe.each([ ])( "Context menu button is not shown when enrollment status is not Completed or Enrolled", ({ status }) => { + setupUserApis() renderWithProviders( , ) @@ -457,13 +521,16 @@ describe.each([ ])( "CoursewareButton switches to Enroll functionality when enrollment status is not enrolled or undefined", ({ status }) => { + setupUserApis() const course = dashboardCourse() course.enrollment = { id: faker.number.int(), status: status, mode: EnrollmentMode.Audit, } - renderWithProviders() + renderWithProviders( + , + ) const card = getCard() const coursewareButton = within(card).getByTestId("courseware-button") @@ -479,58 +546,91 @@ describe.each([ }, ) - test("CoursewareButton hits enroll endpoint appropriately", async () => { - const course = dashboardCourse({ - coursewareId: faker.string.uuid(), - enrollment: { - id: faker.number.int(), - status: EnrollmentStatus.NotEnrolled, - }, - }) + const setupEnrollmentApis = (opts: { + user: ReturnType + course: ReturnType + }) => { + setMockResponse.get(mitxonline.urls.userMe.get(), opts.user) - // Mock user without country and year_of_birth to trigger JustInTimeDialog - const baseUser = mitxonline.factories.user.user() - const mitxUserWithoutRequiredFields = { - ...baseUser, - legal_address: { ...baseUser.legal_address, country: undefined }, - user_profile: { ...baseUser.user_profile, year_of_birth: undefined }, - } - setMockResponse.get( - mitxonline.urls.userMe.get(), - mitxUserWithoutRequiredFields, + const enrollmentUrl = mitxonline.urls.b2b.courseEnrollment( + opts.course.coursewareId ?? undefined, ) + setMockResponse.post(enrollmentUrl, { + result: "b2b-enroll-success", + order: 1, + }) - setMockResponse.post( - mitxonline.urls.b2b.courseEnrollment(course.coursewareId ?? undefined), - { result: "b2b-enroll-success", order: 1 }, - ) - // Mock countries data needed by JustInTimeDialog - setMockResponse.get(mitxonline.urls.countries.list(), [ + const countries = [ { code: "US", name: "United States" }, { code: "CA", name: "Canada" }, - ]) + ] + if (opts.user.legal_address?.country) { + countries.push({ + code: opts.user.legal_address.country, + name: "User's Country", + }) + } + // Mock countries data needed by JustInTimeDialog + setMockResponse.get(mitxonline.urls.countries.list(), countries) + return { enrollmentUrl } + } + + const ENROLLMENT_TRIGGERS = [ + { trigger: "button" as const }, + { trigger: "title-link" as const }, + ] + test.each(ENROLLMENT_TRIGGERS)( + "Enrollment for complete profile bypasses just-in-time dialog", + async ({ trigger }) => { + const userData = mitxUser() + const course = dashboardCourse({ + enrollment: { status: EnrollmentStatus.NotEnrolled }, + }) + const { enrollmentUrl } = setupEnrollmentApis({ user: userData, course }) + renderWithProviders( + , + ) + const card = getCard() + const triggerElement = + trigger === "button" + ? within(card).getByTestId("courseware-button") + : within(card).getByRole("link", { name: course.title }) - renderWithProviders() - const card = getCard() - const coursewareButton = within(card).getByTestId("courseware-button") + await user.click(triggerElement) - // Now the button should show the JustInTimeDialog instead of directly enrolling - await user.click(coursewareButton) + expect(mockAxiosInstance.request).toHaveBeenCalledWith( + expect.objectContaining({ method: "POST", url: enrollmentUrl }), + ) + }, + ) - // Verify the JustInTimeDialog appeared - const dialog = await screen.findByRole("dialog", { - name: "Just a Few More Details", - }) - expect(dialog).toBeInTheDocument() - - // The enrollment API should NOT be called yet (until dialog is completed) - expect(mockAxiosInstance.request).not.toHaveBeenCalledWith( - expect.objectContaining({ - method: "POST", - url: mitxonline.urls.b2b.courseEnrollment( - course.coursewareId ?? undefined, - ), - }), - ) - }) + test.each( + cartesianProduct(ENROLLMENT_TRIGGERS, [ + { userData: mitxUser({ legal_address: { country: "" } }) }, + { userData: mitxUser({ user_profile: { year_of_birth: null } }) }, + ]), + )( + "Enrollment for complete profile bypasses just-in-time dialog", + async ({ trigger, userData }) => { + const course = dashboardCourse({ + enrollment: { status: EnrollmentStatus.NotEnrolled }, + }) + setupEnrollmentApis({ user: userData, course }) + renderWithProviders( + , + ) + const card = getCard() + const triggerElement = + trigger === "button" + ? within(card).getByTestId("courseware-button") + : within(card).getByRole("link", { name: course.title }) + + await user.click(triggerElement) + + await screen.findByRole("dialog", { name: "Just a Few More Details" }) + expect(mockAxiosInstance.request).not.toHaveBeenCalledWith( + expect.objectContaining({ method: "POST" }), + ) + }, + ) }) diff --git a/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.tsx b/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.tsx index e348c2bea1..1772f2f18d 100644 --- a/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.tsx +++ b/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.tsx @@ -120,6 +120,7 @@ const useOneClickEnroll = () => { const userCountry = mitxOnlineUser.data?.legal_address?.country const userYearOfBirth = mitxOnlineUser.data?.user_profile?.year_of_birth const showJustInTimeDialog = !userCountry || !userYearOfBirth + const mutate = ({ href, coursewareId, diff --git a/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardDialogs.test.tsx b/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardDialogs.test.tsx index feae79ed00..8558409bbf 100644 --- a/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardDialogs.test.tsx +++ b/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardDialogs.test.tsx @@ -215,7 +215,9 @@ describe("JustInTimeDialog", () => { test("Opens just-in-time dialog when enrolling with incomplete mitxonline user data", async () => { const { course } = setupJustInTimeTest() - renderWithProviders() + renderWithProviders( + , + ) const enrollButtons = await screen.findAllByTestId("courseware-button") await user.click(enrollButtons[0]) // Use the first (desktop) button @@ -250,7 +252,9 @@ describe("JustInTimeDialog", () => { "Dialog pre-populates with user data if available", async ({ userOverrides, expectCountry, expectYob }) => { const { course } = setupJustInTimeTest({ userOverrides }) - renderWithProviders() + renderWithProviders( + , + ) const enrollButtons = await screen.findAllByTestId("courseware-button") await user.click(enrollButtons[0]) // Use the first (desktop) button const dialog = await screen.findByRole("dialog", { @@ -265,7 +269,9 @@ describe("JustInTimeDialog", () => { test("Validates required fields in just-in-time dialog", async () => { const { course } = setupJustInTimeTest() - renderWithProviders() + renderWithProviders( + , + ) const enrollButtons = await screen.findAllByTestId("courseware-button") await user.click(enrollButtons[0]) // Use the first (desktop) button @@ -296,7 +302,9 @@ describe("JustInTimeDialog", () => { test("Generates correct year of birth options (minimum age 13)", async () => { const { course } = setupJustInTimeTest() - renderWithProviders() + renderWithProviders( + , + ) const enrollButtons = await screen.findAllByTestId("courseware-button") await user.click(enrollButtons[0]) // Use the first (desktop) button @@ -321,7 +329,9 @@ describe("JustInTimeDialog", () => { test("Shows expected countries in country dropdown", async () => { const { course, countries } = setupJustInTimeTest() - renderWithProviders() + renderWithProviders( + , + ) const enrollButtons = await screen.findAllByTestId("courseware-button") await user.click(enrollButtons[0]) // Use the first (desktop) button @@ -344,7 +354,9 @@ describe("JustInTimeDialog", () => { test("Cancels just-in-time dialog without making API calls", async () => { const { course } = setupJustInTimeTest() - renderWithProviders() + renderWithProviders( + , + ) const enrollButtons = await screen.findAllByTestId("courseware-button") await user.click(enrollButtons[0]) // Use the first (desktop) button @@ -372,7 +384,9 @@ describe("JustInTimeDialog", () => { userOverrides: { user_profile: { year_of_birth: 1988 } }, }) - renderWithProviders() + renderWithProviders( + , + ) const enrollButtons = await screen.findAllByTestId("courseware-button") await user.click(enrollButtons[0]) // Use the first (desktop) button diff --git a/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardDialogs.tsx b/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardDialogs.tsx index b0510a6aa8..8a10b4aa9e 100644 --- a/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardDialogs.tsx +++ b/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardDialogs.tsx @@ -30,10 +30,6 @@ const BoldText = styled.span(({ theme }) => ({ ...theme.typography.subtitle1, })) -const SpinnerContainer = styled.div({ - marginLeft: "8px", -}) - const SelectPlaceholder = styled("span")(({ theme }) => ({ color: theme.custom.colors.silverGrayDark, })) @@ -88,17 +84,13 @@ const EmailSettingsDialogInner: React.FC = ({ variant="primary" type="submit" disabled={!formik.dirty || updateEnrollment.isPending} + endIcon={ + updateEnrollment.isPending ? ( + + ) : undefined + } > Save Settings - {updateEnrollment.isPending && ( - - - - )} } @@ -169,17 +161,13 @@ const UnenrollDialogInner: React.FC = ({ variant="primary" type="submit" disabled={destroyEnrollment.isPending} + endIcon={ + destroyEnrollment.isPending ? ( + + ) : undefined + } > Unenroll - {destroyEnrollment.isPending && ( - - - - )} } @@ -275,13 +263,13 @@ const JustInTimeDialogInner: React.FC<{ href: string; readableId: string }> = ({ variant="primary" type="submit" disabled={formik.isSubmitting} + endIcon={ + formik.isSubmitting ? ( + + ) : undefined + } > Submit - {formik.isSubmitting && ( - - - - )} } diff --git a/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/EnrollmentDisplay.tsx b/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/EnrollmentDisplay.tsx index a8b390335c..c5709ee8f8 100644 --- a/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/EnrollmentDisplay.tsx +++ b/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/EnrollmentDisplay.tsx @@ -139,6 +139,7 @@ const EnrollmentExpandCollapse: React.FC = ({ {shownEnrollments.map((course) => ( = ({ {hiddenEnrollments.map((course) => ( ))} @@ -326,7 +326,7 @@ const OrgProgramDisplay: React.FC<{ dashboardResource={course} courseNoun="Module" offerUpgrade={false} - titleHref={course.run?.coursewareUrl} + titleAction="courseware" buttonHref={course.run?.coursewareUrl} /> ))}