Updated InfoBox for Course Product Pages#3523
Conversation
OpenAPI Changes24 changes: 0 error, 9 warning, 15 info Unexpected changes? Ensure your branch is up-to-date with |
There was a problem hiding this comment.
Pull request overview
Updates the MITxOnline course product page InfoBox experience in frontends/main to support the new “Choose Your Path” layout, run-dependent metadata/enrollment behavior, and a session selector for multi-run courses.
Changes:
- Introduces a run-scenario model (
courseRun.ts) plus new hooks (useCourseEnrollment,useCourseEnrolledRunIds,useCertificatePrice) to drive run-dependent enrollment UI and enrolled-state collapse. - Replaces the legacy single
CourseEnrollmentButtonwith a newCourseEnrollAreathat renders Certificate/Learn-for-free “track” cards and anEnrolledlink state. - Adds
SessionSelectand updatesCourseSummary/InfoBoxCourse/CoursePageso the selected run is shared between the InfoBox and the page-header CTA.
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| frontends/main/src/app-pages/ProductPages/useCourseEnrollment.ts | New hook mapping run + user state into enroll-area UI state and click handlers. |
| frontends/main/src/app-pages/ProductPages/useCourseEnrollment.test.tsx | Unit tests for enrollment state mapping, auth gating, and action behavior. |
| frontends/main/src/app-pages/ProductPages/useCourseEnrolledRunIds.ts | Auth-gated query helper for enrolled run IDs scoped to the current course. |
| frontends/main/src/app-pages/ProductPages/useCertificatePrice.tsx | Hook for certificate price display and financial-aid state for a selected run. |
| frontends/main/src/app-pages/ProductPages/TrackCard.tsx | Shared structural component for track cards (Certificate Track / Learn for Free). |
| frontends/main/src/app-pages/ProductPages/SessionSelect.tsx | Session dropdown UI for multi-run courses with annotations (enrolled/cert unavailable/start anytime). |
| frontends/main/src/app-pages/ProductPages/SessionSelect.test.tsx | Tests for SessionSelect labeling, annotations, and ordering. |
| frontends/main/src/app-pages/ProductPages/ProductSummary.tsx | Refactors course summary to be selected-run driven; adds session-row rendering support and deadline/archived messaging. |
| frontends/main/src/app-pages/ProductPages/ProductSummary.test.tsx | Updates tests for selected-run behavior, archived/deadline-passed UI, and payment deadline rendering. |
| frontends/main/src/app-pages/ProductPages/LearnForFreeCard.tsx | New “Learn for Free” track card component. |
| frontends/main/src/app-pages/ProductPages/LearnForFreeCard.test.tsx | Tests for LearnForFreeCard rendering and optional deadline note. |
| frontends/main/src/app-pages/ProductPages/InfoBoxCourse.tsx | Rebuilds InfoBox layout as a count-aware grid, wires in session selector and new enroll area. |
| frontends/main/src/app-pages/ProductPages/InfoBoxCourse.test.tsx | Integration-style tests for session switching, enrolled collapse, and grid structure/count attributes. |
| frontends/main/src/app-pages/ProductPages/EnrolledLink.tsx | New reusable “Enrolled” button-link component. |
| frontends/main/src/app-pages/ProductPages/courseRun.ts | New helpers/types for selecting runs, ordering runs, and deriving offering/status scenario. |
| frontends/main/src/app-pages/ProductPages/courseRun.test.ts | Tests for run selection and scenario derivation helpers. |
| frontends/main/src/app-pages/ProductPages/CoursePage.tsx | Lifts selected run state to the page so header CTA and InfoBox stay in sync; updates outline query gating. |
| frontends/main/src/app-pages/ProductPages/CoursePage.test.tsx | Updates/extends tests to cover new header CTA behavior and session syncing. |
| frontends/main/src/app-pages/ProductPages/CourseEnrollmentButton.tsx | Removes legacy single-button enrollment implementation. |
| frontends/main/src/app-pages/ProductPages/CourseEnrollmentButton.test.tsx | Removes legacy tests tied to CourseEnrollmentButton. |
| frontends/main/src/app-pages/ProductPages/CourseEnrollArea.tsx | New enroll-area renderer with track cards, CTA placement rules, enrolled collapse, and error handling. |
| frontends/main/src/app-pages/ProductPages/CourseEnrollArea.test.tsx | Tests for offering layouts, degraded states, loading state, and financial-aid display behavior. |
| frontends/main/src/app-pages/ProductPages/CertificateTrackCard.tsx | New “Certificate Track” card component with optional financial-aid link. |
| frontends/main/src/app-pages/ProductPages/CertificateTrackCard.test.tsx | Tests for CertificateTrackCard rendering, bullets, and financial-aid link behavior. |
f3fe8a5 to
ed67b74
Compare
| loading={isStatusLoading} | ||
| pending={isPending} | ||
| variant="bordered" | ||
| announceStatus={false} |
There was a problem hiding this comment.
it looks like announceStatus={false} bypasses the busyProps check in CourseEnrollArea. I think you want to remove it so the header CTA gets aria-busy={isBusy} during loading/pending states.
|
|
||
| const { state, isStatusLoading, isPending } = useCourseEnrollment( | ||
| course, | ||
| selectedRun, |
There was a problem hiding this comment.
What happens if there is an error?
There was a problem hiding this comment.
Looks good. I was able to verify all the scenarios. Thank you for included the script that was very helpful. I left one question and one a11y issue for you to look at but don't see any blockers.
| Scenario | Course title | Direct URL | Screenshot |
|---|---|---|---|
| E1 - Logged in as student_infobox@odl.local | [QA] Both paths — free audit + paid certificate (single run) | /courses/course-v1:qa-a1 | ![]() |
| E2 - Logged in as student_infobox@odl.local | [QA] Multiple sessions — Session selector (all Both) | /courses/course-v1:qa-b1 | ![]() |
| C2 - Logged in as student_infobox@odl.local | [QA] Financial assistance — available (anon) / applied (approved user) | /courses/course-v1:qa-c-finaid | ![]() |
061f5ba to
ce9d858
Compare
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…run ids) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nroll Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a purely presentational paid-path card used in the Course InfoBox redesign (PR1). Accepts an opaque price ReactNode, optional financialAid link, productNoun for bullet copy, and optional embedded action node. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Presentational dropdown for selecting a course session (enrollable run). Built on SimpleSelectField with accessible 'Session' label; converts run ids at the boundary (String on options, Number in onChange). Shows 'Anytime' for self-paced past-start runs; appends '— Enrolled' for runs the user is already enrolled in. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CourseSummary now accepts selectedRun prop (CourseRunV2 | undefined) instead of deriving the run from course.next_run_id internally - Adds optional sessionSelect prop rendered in the dates row slot - Payment deadline line extracted from CoursePriceRow into CourseSummary, rendered beneath dates when selectedRun has upgrade_deadline and !is_archived - Price/financial-assistance content removed from CourseSummary (now in cards) - CoursePriceRow, CourseCertificateBox and their helpers deleted (file-local, now unused after removing from CourseSummary) - InfoBoxCourse: stopgap one-liner passes getSelectedRun(course) as selectedRun; Task 7b will wire real selection state Deleted test coverage: - "Price Row" describe block: behavior moved to CertificateTrackCard (Task 3/4) - "Financial Assistance" describe block: same — already covered by card tests These deletions are intentional; coverage lives in CertificateTrackCard.test.tsx Open item: no-runs Alert "Learn More" link — no destination URL defined in codebase for the no-runs case; left without link pending design input. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ceRow testid Collapsed filter in CourseDatesRow was keying off course.next_run_id instead of the passed-in nextRun prop, so selecting a non-default run showed the wrong dates. Fixed to use nextRun.id. TestIds.PriceRow is still live (used by ProgramPriceRow), so no removal. Added divergence test: next_run_id != selectedRun → collapsed row shows selectedRun's date. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Track card Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wires InfoBoxCourse with selectedRunId state, useCourseEnrolledRunIds, SessionSelect (multi-run only), CourseEnrollArea, and a BoxGrid styled component that implements the count-aware responsive CSS grid (data-boxes 1|2|3 drives meta-span and single-column fallback at tablet breakpoint). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… Choose Your Path heading C1: paidOnly/freeOnly/deadlinePassed/archived scenarios now wrap their card + below-button in a single div (data-card="paid"|"free"), making each offering exactly one direct grid child. The both-case wrappers were already correct. I1: Add data-choose-path to the "Choose Your Path" heading in CourseEnrollArea, then target it with grid-column: 1/-1 in InfoBoxCourse's BoxGrid so it spans both columns in the 3-box (both) tablet layout. Test: add structural assertion in InfoBoxCourse.test.tsx confirming paidOnly grid has exactly 2 direct children and that the Enroll button shares the same wrapper as the Certificate Track card. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…from course page Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… run) The InfoBox now renders enroll-card h3 headings depending on the course run's enrollability, which makeCourse() randomizes. Pin a non-enrollable run so the page heading outline is deterministic while the outline section (which needs a run with courseware_id) still renders. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… popover Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Delete orphaned CourseEnrollmentButton.tsx and its test file — no live importer remains after Tasks 7b and 8 rewired the Course page and InfoBox to use CourseEnrollArea/EnrollButton directly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… label; review cleanups
- access kind in makeOnClick now takes the same audit-enroll + dashboard-redirect path as
free, fixing the silent no-op on "Access Course Materials" clicks (archived scenario)
- deadlinePassed option changed from kind:"free"/label:"Start Learning" to
kind:"access"/label:"Access Course Materials" for label consistency with archived
- useCourseEnrolledRunIds: remove dead ?? false on always-boolean isLoading/isError
- InfoBoxCourse: fix comment referencing .choose-path-heading → [data-choose-path]
- CertificateTrackCard: FeatureIcon styled via callback form (({ theme }) => ...) for
consistency with LearnForFreeCard; remove module-level theme import
- Tests: add archived-enroll regression guard; update deadlinePassed label assertions;
replace FIX4 no-op smoke tests with real assertions or removal
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…st placement tests, polish Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Metadata: reorder Format → Estimated → Session; "Course Format" → "Format" on the course row; payment deadline recolored to darkGray2/body3 and moved into a 3-col SessionRow grid beneath the dropdown. Session control: bare smoot Select + inline bold "Session:" label (labelId→aria-labelledby), size=medium (40px), full-width. Enroll area: "Choose Your Path" → subtitle2; full-width buttons; OfferingCell gives a 16px card↔button gap; secondary variant only in the "both" case, primary everywhere else. Cards: financial-assistance moves into the feature list as a 4th bullet; LearnForFreeCard border silverGrayLight → lightGray2. Structure: lightGray2 <hr> divider between metadata and Choose-Your-Path (hidden in tablet 2-col grid); header button text restored to darkGray2. Tests updated for "Format" label and the new divider element. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… dates, header↔infobox run sync
- SessionSelect: bump label→dropdown gap to 16px (icon→label stays 8px) via
marginRight on the label; collapse the redundant start-year for same-year
date ranges ("Sep 8 - Dec 16, 2026"), keep both years across a year boundary.
- ProductSummary: archived single-run courses lead the dates row with
"Course content available anytime" + the end date instead of a stale start
date (gated to the single-date view; multi-date lists keep concrete dates).
- Lift selectedRunId to CoursePage so the header CTA tracks the session chosen
in the InfoBox. CourseInfoBox is now controlled (selectedRunId/onSelectRun);
the header receives the shared selectedRun.
Tests: same/cross-year range formatting, single-run archived dates row, and a
CoursePage correlation test (header CTA follows the InfoBox session switch).
InfoBoxCourse tests use a ControlledInfoBox wrapper for the lifted state.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ement Visual-QA fixes against the canonical Figma frames (A4/A5/A6): - Deadline-passed (A4): add the yellow "Certificate deadline has passed." alert; lead the date row with "Course content available anytime" + End; suppress the payment-deadline line (no upcoming payment once the window closed). Matches frame 39861:102467. - Free card: the "Certificate deadline passed" note is now bold + underlined and sits above the access bullet; show it for archived runs too (their certificate window has also closed), not only deadline-passed. - Warning placement: move the degraded-state notices (no-sessions, archived, deadline-passed) to the bottom of the metadata block, just above the offerings, matching Figma. They were rendered at the top — pre-existing placement the spec never pinned. Tests: update assertions for the new note string/placement; add a deadline-passed describe block (alert / available-anytime date row / suppressed payment deadline); pin the enrollment scenario in the date-row and payment-deadline tests so factory randomness can't land on deadline-passed now that it renders distinctly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rchived cert-note gating, a11y - deadline-passed keeps its normal date row; "content available anytime" archived-only - archived "Certificate deadline passed" note gated on the run having offered a cert - drop the in-card note for deadline-passed (redundant with its alert) - a11y: accessible name on the loading enroll button; accessible framing for the discounted certificate price (mirrors the program path) - test: pin multi-run payment-deadline placement + no-runs section-divider absence - reuse the dateLoading skeleton constant Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…art Anytime' annotation, truncate collapsed value - dropdown options always render the date range; self-paced past-start runs append an italic '— Start Anytime' annotation instead of a bare 'Anytime' (multiple such runs were previously indistinguishable) - collapsed value drops the redundant '— Enrolled' marker and ellipsis-truncates, fixing overflow in the narrow sidebar column Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… line, sort by start date Drop the "— Start Anytime" annotation from the dropdown's collapsed value (it overflowed the narrow sidebar like "— Enrolled" did); the collapsed value is now dates only. Surface the anytime nature as its own line under the dropdown (above the payment-deadline line) via SessionAnnotation in CourseSummary, gated on the selected run starting anytime. Drop the italics on "Start Anytime" everywhere. Order dropdown options descending by start date (future sessions near the top; null start last) via a byStartDateDesc comparator, mirroring the "More Dates" list; the default selection still follows next_run_id. Spec §4g updated with the ordering, the collapsed dates-only rule, the dedicated anytime line, and an explicit note that these selector affordances deviate from Figma (not depicted there). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Start Anytime + payment deadline were two separate grid rows under the session dropdown, each separated by the row's full 8px gap — reading as airy whitespace. Group them into a single SessionSubText block with a tight 2px inter-line gap; the dropdown→note spacing (the row's 8px rowGap) is unchanged. They now read as one unit of secondary text while payment deadline keeps its own line and weight. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…eadline-passed too Previously the in-card note was archived-only — dropped for deadline-passed as redundant with that scenario's own "Certificate deadline has passed." alert. But that made the two passed-deadline states inconsistent: deadline-passed conveyed it only via the alert, archived via the in-card line. Show the in-card line in both (gated on the run having offered a certificate), matching Figma frame 39861:102467, which shows both the alert and the in-card line. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rtificate available)" Mirror the legacy enrollment dialog (CourseProductDetailEnroll.js): a run whose certificate can no longer be purchased (is_upgradable === false) appends a "(no certificate available)" note to its dropdown option, so a user choosing a session knows before enrolling. Open-menu options only; the collapsed value stays dates-only. Tests asserting exact date labels now pin is_upgradable since the factory randomizes it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…, dedup)
Five Minor findings from the multi-lens review:
- InfoBoxCourse: count an enrolled run as one offering box even when its
scenario is "none" (enrolled collapse supersedes degraded scenarios, §4h).
Previously `scenario === "none"` was checked before `isEnrolled`, so an
enrolled user on a paid-only-expired run rendered the Enrolled link but
counted 0 boxes (data-boxes=1, divider suppressed). New box-count test.
- CourseEnrollArea: pin the busy button's accessible name ("Loading") in the
loading-state test — a regression dropping the aria-label would have stayed
green (WCAG 4.1.2).
- courseRun: hoist runStartsAnytime and byStartDateDesc into the shared module;
SessionSelect and ProductSummary now import them instead of keeping
byte-identical copies, and CourseDatesRow uses the shared sort comparator.
- CourseEnrollArea: wrap the busy LoadingSpinner in <span aria-hidden> so the
spinner is actually hidden from assistive tech — ol-components LoadingSpinner
drops aria-hidden, leaking a redundant role=progressbar named "Loading".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… union
getCourseScenario now returns a discriminated union with two orthogonal facets —
what's enrollable (offering: none|free|paid|both) and the lifecycle (status:
active|deadlinePassed|archived) — instead of a flat 6-value enum. Typed so the
impossible combinations are unrepresentable (a degraded run can't offer a
purchasable certificate; archived is always audit-only). Consumers simplify where
the old enum forced "archived || deadlinePassed" repetition:
- suppressPaymentDeadline / deadlineNote key off `status !== "active"`
- offeringBoxes keys off `offering`
- useCourseEnrollment builds options from `offering` (paid + free tracks) instead
of a 6-case switch; the free label is derived from status
Behavior change: a paid-only run past its deadline is now {deadlinePassed, none}
and surfaces the "Certificate deadline has passed." warning (no enroll button)
instead of silently rendering nothing.
Also folds in two earlier-agreed changes that touch the same files:
- collapse EnrollActionKind "access" into "free" (identical free-enrollment path;
only the button label differed)
- equal-height offering cards in the tablet "both" layout, driven from BoxGrid CSS
(fills each card's flex chain to its stretched cell, CTA pinned to the bottom)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… refs Review-round-3 fixes (all behavior-preserving except the new test): - getCourseScenario now owns `offeredCertificate` on its degraded variants; CourseEnrollArea reads the flag instead of re-running getEnrollmentType, so enrollment-mode parsing stays in courseRun.ts. - Add an offeringBoxCount(scenario, isEnrolled) helper as the single owner of the box-count mapping; InfoBoxCourse consumes it instead of a hand-mirrored ternary. - Drop the unused `disabled` field from EnrollAction — busy state already lives on EnrollButton via the pending/loading props. - Add a hook test pinning the approved paid-only-past-deadline behavior (offering "none" → no enroll button) at the offering→button mapping layer. - Remove comments that pointed at the gitignored spec (§4g/§4h/§5, "the spec pinned…"), inlining the rationale instead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…m card DOM The Certificate Track and Learn for Free cards duplicated ~80 lines of identical styled-component scaffold, and BoxGrid reached three levels into that private DOM (`[data-card] > * > * > :last-child`) to equalize card height in the side-by-side layout. - Extract a shared TrackCard primitive (variant-driven surface, header, price, optional note slot, feature bullets, action) that both cards compose. - Move the fill-to-stretch + bottom-align-CTA behavior into TrackCard via a `fill` prop the cards opt into for the "both" layout; remove the DOM-structure-coupled CSS from BoxGrid. Behavior-preserving — `fill` is a no-op wherever the grid cell is only content-height. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review-round-4 comment hygiene — strip process/temporal grounding and redundancy while keeping the durable rationale: - Drop the dead `CourseProductDetailEnroll` legacy-symbol citation in getCourseScenario (the symbol isn't in this repo). - Replace "matching Figma (…leaves placement open)" with the actual spatial reason the degraded notices sit where they do. - Drop cross-references that couple a comment to a sibling's current impl (useCertificatePrice → ProductSummary program-price path). - De-duplicate the offeringBoxCount contract (kept on the helper's doc, trimmed at the call site) and the byStartDateDesc note (kept on the comparator's doc). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rikethrough On the course Certificate Track card, financial aid no longer discounts the displayed price (the struck-through original / discounted final is removed). Strikethrough already means "purchased separately" on program cards, so reusing it for finaid overloaded the same visual with two meanings. Now the card always shows the full certificate price and conveys finaid as text: - approved → "Financial assistance approved (applied at checkout)" (the discount applies later, in checkout; an approved-but-$0 discount is an accepted edge) - not yet approved → "Financial assistance available" Both states keep linking to the financial-aid form. Course-only; the program price path is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…etch enrolled button Two tablet-only (sm–md) fixes: - SummaryRows: the 2-column metadata split only reads well full-width. In the 2-box course layout the metadata sits in a half-width grid cell, so the split crammed the rows. Gate it behind a $tabletColumns prop; CourseInfoBox passes 1 when boxCount === 2. - Enrolled state: EnrolledLink was a bare grid child and stretched to the tall metadata column's height. Wrap it in OfferingCell so it keeps its natural button height pinned to the top, like the other offering boxes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Four review-identified consolidations, each a parameterized table with meaningful per-row labels. No coverage change (still 318 tests) — every row pins the same distinct branch the standalone test did; this only removes copy-pasted setup/assertion boilerplate: - useCourseEnrollment: 7 scenario→label/kind tests → one table (the biggest boilerplate source; degraded rows still assert their CourseScenario shape). - InfoBoxCourse: 3 not-enrolled data-boxes tests → one table (kept the no-runs and the two enrolled cases separate — distinct setup/assertions). - CoursePage: 4 header-scenario tests → one table (kept enrolled + the header↔InfoBox run-sync test separate). - CertificateTrackCard + CourseEnrollArea: the finaid available/approved pairs → tables (kept CourseEnrollArea's "full price, not a discount" test separate). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The finaid tests spelled out the full product_flexible_price discount object,
but "approved" only keys off product_flexible_price?.id (useCertificatePrice),
and the flexiblePrice factory already defaults that nested discount to a real
id. So:
- approved link case → makeFlexiblePrice() (factory default is "approved")
- available case → makeFlexiblePrice({ product_flexible_price: null })
- full-price test → makeDiscount({ discount_type, amount }); only the $25
dollars-off is under test (it's the would-be $75 that must NOT render), the
factory fills the rest. Dropped the unused id/price overrides.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A second, broader sweep (not anchored on the product_flexible_price pattern)
found three more:
- ProductSummary: hand-rolled 11-field product_flexible_price discount literal →
makeDiscount({ amount, discount_type }); only those two fields drive the
asserted $125/$200/"applied", the factory fills the rest.
- ProductSummary: product({ price: "1499.00" }) → product(); the test asserts
formatPrice(product.price), so the specific value was never needed.
- CoursePage: product({ price: "500" }) → product(); the price is never asserted
(the test only checks the program-bundle upsell renders).
Verified no flakiness: scripts/test/jest-repeat.sh 30 over both files (varied
seeds) — all green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
formatDate renders in the local timezone, so a run timestamp near a day boundary formatted to a different calendar date on the (UTC) server than in the browser — a hydration error on the collapsed dropdown value (and option labels). Wrap the date-range output in <NoSSR> (the same pattern LocalDate and the payment-deadline line already use), so the server and first client paint render nothing and the local date fills in after mount. Local-tz formatting is kept, so the dropdown stays consistent with the date row's LocalDate output. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Swap the hardcoded `#004D1A` in ProgramStartForFreeBox's gradient for `theme.custom.colors.darkGreen` (exact match). Wires the previously-unused theme arg through. No visual change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
isError previously OR'd in enrollmentsIsError (failure to load the user's enrolled-run list). That made the enroll area show "There was a problem processing your enrollment" when the user hadn't attempted to enroll — only the read of their enrollment status failed. Limit isError to actual basket / create-enrollment action failures; a status-load failure now degrades silently (user treated as not-enrolled). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rebasing onto main pulled in the trackViewCoursePage/trackCourseProgramView useEffect (PR #3524). The CoursePage test's partial gtm mock only stubbed trackCourseEnrolled, so those two became undefined and the effect threw. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ce9d858 to
fb428ce
Compare













What are the relevant tickets?
For
Only implements the updated course info boxes, not the program (or program-as-course) infoboxes.
Description (What does it do?)
Updates the infoboxes:
next_run_id.Changes at a glance
courseRun.ts) — pure functions that map a run to the case it should display (free / paid / both, deadline-passed, archived…).useCourseEnrollment,useCourseEnrolledRunIds,useCertificatePrice) — enrollment state machine, enrolled-run lookup, and certificate pricing/financial-aid.TrackCard+CertificateTrackCard/LearnForFreeCard,SessionSelect,EnrolledLink, summary rows) — stateless, just render what they're handed.InfoBoxCourse,CourseEnrollArea,CoursePage) — wire the hooks + scenario into the presentational pieces.Screenshots (if appropriate):
Each pair is main (before) on the left, this PR (after) on the right, at
the desktop sidebar width where the InfoBox renders. Based on a subset of the course from https://gist.github.com/ChristopherChudzicki/712a9ab4a2e065a8fa02ff35126388bb
Single run, various cases
A1 · Both paths — free audit + paid certificate (anon)
A2 · Paid only — certificate, no free audit (anon)
A3 · Free only — audit, no certificate (anon)
A7 · Instructor-Paced course
A8 · Self-Paced course starting in future
Warnings (upgrade deadline passed, archived course, no runs)
A4 · Certificate deadline passed — audit still available (anon)
A5 · Archived — content access only (anon)
A6 · No open sessions — warning, no enrollment (anon)
Multiple runs
B1 · Multiple sessions — session selector (anon)
With the session list expanded — main shows a plain expanded date list, this PR shows the session selector dropdown:
Financial aid
C1 · Financial assistance available (anon)
C2 · Financial assistance applied — approved user (logged in)
Enrolled states
E1 · Enrolled — collapses to a single link (logged in)
E2 · Enrolled in one of multiple sessions (logged in)
Tablet
Tablet — A1 · Both paths (anon)
Tablet — A2 · Paid only (anon)
Tablet — E1 · Enrolled (logged in)
How can this be tested?
Prerequisites: MITxOnline and Learn integrated locally.
Test Data: I tested a lot of scenarios above. I think at least the following is worth setting up:
Note: All these scenarios are covered in https://gist.github.com/ChristopherChudzicki/712a9ab4a2e065a8fa02ff35126388bb, if you want to use it.
Different enrollment mode combos:
A course with multiple enrollable runs Script's B1
upgrade_deadline)An instructor-paced course Script's A7
A self-paced course that starts in future Script's A8
Manual Testing:
Additional Context
Program update portion of https://github.com/mitodl/hq/issues/11787 will be handled separately.