From a07bd2942180d80cc5631cc074eb5d4ce51c02ee Mon Sep 17 00:00:00 2001 From: Muhammad Anas Date: Fri, 3 Jul 2026 21:01:32 +0500 Subject: [PATCH 1/5] feat: MIT OL header/footer overrides for frontend-base MFEs (rebased onto main) Rebase the header/footer overrides onto current main. main already carries the shared header/footer wiring in the site configs (via the instructor-dashboard work), so this brings only the branch's own deliverables on top of it: - shared/header/index.tsx (MITx / MITxOnline / xPRO header apps) - shared/footer/index.tsx (flat single-row footer matching the legacy learning MFE) - shared/styles/mitxonline.scss - public/index.html viewport meta tags (mitx / mitxonline / xpro) Configs are taken from main unchanged (they already wire these apps + the instructor dashboard). History squashed from 7 commits because the site-config rewrites conflicted irreconcilably with main's merged versions. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../frontend/mitx/public/index.html | 2 + .../frontend/mitxonline/public/index.html | 2 + .../frontend/shared/src/footer/index.tsx | 216 ++++++++++++------ .../frontend/shared/src/header/index.tsx | 101 +++++++- .../shared/src/styles/mitxonline.scss | 26 ++- .../frontend/xpro/public/index.html | 2 + 6 files changed, 264 insertions(+), 85 deletions(-) diff --git a/deployments/mit-ol/mfe_slot_config/frontend/mitx/public/index.html b/deployments/mit-ol/mfe_slot_config/frontend/mitx/public/index.html index 870979a8..12903b85 100644 --- a/deployments/mit-ol/mfe_slot_config/frontend/mitx/public/index.html +++ b/deployments/mit-ol/mfe_slot_config/frontend/mitx/public/index.html @@ -1,6 +1,8 @@ + + MITx diff --git a/deployments/mit-ol/mfe_slot_config/frontend/mitxonline/public/index.html b/deployments/mit-ol/mfe_slot_config/frontend/mitxonline/public/index.html index 99a98add..cb104b2e 100644 --- a/deployments/mit-ol/mfe_slot_config/frontend/mitxonline/public/index.html +++ b/deployments/mit-ol/mfe_slot_config/frontend/mitxonline/public/index.html @@ -1,6 +1,8 @@ + + MIT OpenLearning diff --git a/deployments/mit-ol/mfe_slot_config/frontend/shared/src/footer/index.tsx b/deployments/mit-ol/mfe_slot_config/frontend/shared/src/footer/index.tsx index 03a4a0c5..d4647525 100644 --- a/deployments/mit-ol/mfe_slot_config/frontend/shared/src/footer/index.tsx +++ b/deployments/mit-ol/mfe_slot_config/frontend/shared/src/footer/index.tsx @@ -1,10 +1,6 @@ -import { - useSiteConfig, - WidgetOperationTypes, - LayoutOperationTypes, -} from "@openedx/frontend-base"; +import { Slot, useSiteConfig, WidgetOperationTypes } from "@openedx/frontend-base"; import type { App, SlotOperation } from "@openedx/frontend-base"; -import { Hyperlink } from "@openedx/paragon"; +import { Hyperlink, Image } from "@openedx/paragon"; /** * Shape of the MIT OL footer config expected in @@ -19,6 +15,8 @@ export interface MITOLFooterConfig { supportUrl?: string; accessibilityUrl?: string; copyrightText?: string; + footerLogoUrl?: string; + footerLogoDestination?: string; } function useMITOLFooterConfig(): MITOLFooterConfig { @@ -29,46 +27,114 @@ function useMITOLFooterConfig(): MITOLFooterConfig { function CopyrightNotice() { const { copyrightText } = useMITOLFooterConfig(); - if (!copyrightText) return null; - return
{copyrightText}
; -} - -function AboutLink() { - const { aboutUrl } = useMITOLFooterConfig(); - if (!aboutUrl) return null; - return About Us; -} - -function SupportLink() { - const { supportUrl } = useMITOLFooterConfig(); - if (!supportUrl) return null; - return Contact; + return ( +
+ {copyrightText && ( +
{copyrightText}
+ )} +
+ edX and Open edX are registered trademarks of edX LLC. +
+
+ ); } -function AccessibilityLink() { - const { accessibilityUrl } = useMITOLFooterConfig(); - if (!accessibilityUrl) return null; - return Accessibility; +/** + * Footer logo (left side). Reads footerLogoUrl from the runtime config; + * falls back to the shell's default (headerLogoImageUrl) if not set. + */ +function FooterLogo() { + const { footerLogoUrl, footerLogoDestination } = useMITOLFooterConfig(); + const { headerLogoImageUrl, siteName } = useSiteConfig(); + const src = footerLogoUrl || headerLogoImageUrl; + if (!src) return null; + const img = ( + {siteName + ); + if (footerLogoDestination) { + return {img}; + } + return img; } -function PrivacyPolicyLink() { - const { privacyPolicyUrl } = useMITOLFooterConfig(); - if (!privacyPolicyUrl) return null; - return Privacy Policy; +/** Recreation of the shell's PoweredBy widget (it is not exported from the root). */ +function PoweredBy() { + return ( + + Powered by Open edX + + ); } -function TermsOfServiceLink() { - const { termsOfServiceUrl } = useMITOLFooterConfig(); - if (!termsOfServiceUrl) return null; +/** + * Custom desktop footer layout replacing the shell's DesktopFooterLayout so we + * match the legacy learning MFE at every width: a 3-part row of logo (left), + * centered links, and the "Powered by Open edX" logo (right), with the + * copyright/trademark notice on a full-width centered row below. The row is + * intentionally NOT collapsed to a vertical stack on mobile so the placement + * mirrors legacy (the links themselves still stack — see MITOLFooterLinks). + * The shell layout used `justify-content-between` (a paragon-layer `!important` + * utility we cannot override from our site layer), which forced a large gap + * between the links and copyright and pushed the "Powered by" logo to the bottom. + */ +function MITOLDesktopFooterLayout() { return ( - Terms of Service +
+
+
+ +
+
+ +
+
+ +
+
+
+ +
+
); } -function HonorCodeLink() { - const { honorCodeUrl } = useMITOLFooterConfig(); - if (!honorCodeUrl) return null; - return Honor Code; +/** + * Single horizontal row of footer links matching the legacy learning MFE + * (About Us · Terms of Service · Accessibility · Help), centered, with no + * column labels. Each link is omitted if its URL is missing from the runtime + * config. + */ +function MITOLFooterLinks() { + const { aboutUrl, termsOfServiceUrl, accessibilityUrl, supportUrl } = + useMITOLFooterConfig(); + const links = [ + { url: aboutUrl, label: "About Us" }, + { url: termsOfServiceUrl, label: "Terms of Service" }, + { url: accessibilityUrl, label: "Accessibility" }, + { url: supportUrl, label: "Help" }, + ].filter((link): link is { url: string; label: string } => Boolean(link.url)); + if (links.length === 0) return null; + return ( + + ); } /** @@ -79,59 +145,69 @@ function HonorCodeLink() { */ export function createMITOLFooterApp(): App { const slots: SlotOperation[] = [ + // Replace the shell's desktop footer layout with our own so we control the + // link/copyright spacing and place "Powered by Open edX" at the top right. { - slotId: "org.openedx.frontend.slot.footer.desktopLegalNotices.v1", - id: "mitol.footer.copyright", - op: WidgetOperationTypes.APPEND, - component: CopyrightNotice, + slotId: "org.openedx.frontend.slot.footer.desktop.v1", + relatedId: "org.openedx.frontend.widget.footer.desktopLayout.v1", + id: "mitol.footer.desktopLayout", + op: WidgetOperationTypes.REPLACE, + component: MITOLDesktopFooterLayout, }, - // Column 1: Resources + // Remove the shell's default copyright line (e.g. "© 2026 MIT Learn (dev).") + // so only the MIT OL copyright + trademark notice remain, matching the + // legacy learning MFE footer. { - slotId: "org.openedx.frontend.slot.footer.desktopCenterLink1.v1", - op: LayoutOperationTypes.OPTIONS, - options: { label: "Resources" }, + slotId: "org.openedx.frontend.slot.footer.desktopLegalNotices.v1", + relatedId: "org.openedx.frontend.widget.footer.desktopCopyrightNotice.v1", + op: WidgetOperationTypes.REMOVE, }, { - slotId: "org.openedx.frontend.slot.footer.desktopCenterLink1.v1", - id: "mitol.footer.col1.about", + slotId: "org.openedx.frontend.slot.footer.desktopLegalNotices.v1", + id: "mitol.footer.copyright", op: WidgetOperationTypes.APPEND, - component: AboutLink, + component: CopyrightNotice, }, + // Replace the shell's default logo (which uses headerLogoImageUrl) with + // our FooterLogo component that reads footerLogoUrl from the config. { - slotId: "org.openedx.frontend.slot.footer.desktopCenterLink1.v1", - id: "mitol.footer.col1.contact", - op: WidgetOperationTypes.APPEND, - component: SupportLink, + slotId: "org.openedx.frontend.slot.footer.desktopLeftLinks.v1", + relatedId: "org.openedx.frontend.widget.footer.desktopLeftLinksLogo.v1", + id: "mitol.footer.logo", + op: WidgetOperationTypes.REPLACE, + component: FooterLogo, }, + // Single centered horizontal row of links (no column labels) matching + // the legacy learning MFE footer. Append directly to the center-links + // container (CenterLinks layout) instead of a desktopCenterLinkN.v1 slot + // so the links are NOT wrapped in frontend-base's LabeledLinkColumn + // (which forces `small` font + a flex column). Remove the 4 default + // column slots so only our row renders. { - slotId: "org.openedx.frontend.slot.footer.desktopCenterLink1.v1", - id: "mitol.footer.col1.accessibility", - op: WidgetOperationTypes.APPEND, - component: AccessibilityLink, + slotId: "org.openedx.frontend.slot.footer.desktopCenterLinks.v1", + relatedId: "org.openedx.frontend.widget.footer.desktopCenterLink1.v1", + op: WidgetOperationTypes.REMOVE, }, - // Column 2: Policies { - slotId: "org.openedx.frontend.slot.footer.desktopCenterLink2.v1", - op: LayoutOperationTypes.OPTIONS, - options: { label: "Policies" }, + slotId: "org.openedx.frontend.slot.footer.desktopCenterLinks.v1", + relatedId: "org.openedx.frontend.widget.footer.desktopCenterLink2.v1", + op: WidgetOperationTypes.REMOVE, }, { - slotId: "org.openedx.frontend.slot.footer.desktopCenterLink2.v1", - id: "mitol.footer.col2.privacy", - op: WidgetOperationTypes.APPEND, - component: PrivacyPolicyLink, + slotId: "org.openedx.frontend.slot.footer.desktopCenterLinks.v1", + relatedId: "org.openedx.frontend.widget.footer.desktopCenterLink3.v1", + op: WidgetOperationTypes.REMOVE, }, { - slotId: "org.openedx.frontend.slot.footer.desktopCenterLink2.v1", - id: "mitol.footer.col2.tos", - op: WidgetOperationTypes.APPEND, - component: TermsOfServiceLink, + slotId: "org.openedx.frontend.slot.footer.desktopCenterLinks.v1", + relatedId: "org.openedx.frontend.widget.footer.desktopCenterLink4.v1", + op: WidgetOperationTypes.REMOVE, }, { - slotId: "org.openedx.frontend.slot.footer.desktopCenterLink2.v1", - id: "mitol.footer.col2.honor-code", + slotId: "org.openedx.frontend.slot.footer.desktopCenterLinks.v1", + id: "mitol.footer.links", op: WidgetOperationTypes.APPEND, - component: HonorCodeLink, + component: MITOLFooterLinks, }, ]; diff --git a/deployments/mit-ol/mfe_slot_config/frontend/shared/src/header/index.tsx b/deployments/mit-ol/mfe_slot_config/frontend/shared/src/header/index.tsx index 2c014438..f255ab8b 100644 --- a/deployments/mit-ol/mfe_slot_config/frontend/shared/src/header/index.tsx +++ b/deployments/mit-ol/mfe_slot_config/frontend/shared/src/header/index.tsx @@ -3,9 +3,10 @@ import { useSiteConfig, useAuthenticatedUser, WidgetOperationTypes, + Slot, } from "@openedx/frontend-base"; import type { App, SlotOperation } from "@openedx/frontend-base"; -import { Dropdown, Hyperlink, Image } from "@openedx/paragon"; +import { Container, Dropdown, Hyperlink, Image } from "@openedx/paragon"; import { isLearnCourse, isMITxOnlineCourse } from "../utils/courseContext"; // --------------------------------------------------------------------------- @@ -13,18 +14,28 @@ import { isLearnCourse, isMITxOnlineCourse } from "../utils/courseContext"; // --------------------------------------------------------------------------- const SLOT = { + desktop: "org.openedx.frontend.slot.header.desktop.v1", + mobile: "org.openedx.frontend.slot.header.mobile.v1", desktopLeft: "org.openedx.frontend.slot.header.desktopLeft.v1", desktopRight: "org.openedx.frontend.slot.header.desktopRight.v1", mobileCenter: "org.openedx.frontend.slot.header.mobileCenter.v1", + mobileRight: "org.openedx.frontend.slot.header.mobileRight.v1", secondaryLinks: "org.openedx.frontend.slot.header.secondaryLinks.v1", authenticatedMenu: "org.openedx.frontend.slot.header.authenticatedMenu.v1", } as const; const WIDGET = { + desktopLayout: "org.openedx.frontend.widget.header.desktopLayout.v1", + mobileLayout: "org.openedx.frontend.widget.header.mobileLayout.v1", desktopLogo: "org.openedx.frontend.widget.header.desktopLogo.v1", mobileLogo: "org.openedx.frontend.widget.header.mobileLogo.v1", desktopPrimaryLinks: "org.openedx.frontend.widget.header.desktopPrimaryLinks.v1", + desktopAuthenticatedMenu: + "org.openedx.frontend.widget.header.desktopAuthenticatedMenu.v1", + mobileAuthenticatedMenu: + "org.openedx.frontend.widget.header.mobileAuthenticatedMenu.v1", + help: "org.openedx.frontend.widget.header.help.v1", menuProfile: "org.openedx.frontend.widget.header.desktopAuthenticatedMenuProfile.v1", menuAccount: @@ -92,6 +103,48 @@ const UserMenuToggle: FC = () => { ); }; +// --------------------------------------------------------------------------- +// Custom authenticated user menu — replaces the default AvatarButton toggle +// with UserMenuToggle (person icon + display name + chevron) while keeping the +// frontend-base authenticatedMenu slot for the dropdown items. +// --------------------------------------------------------------------------- + +const MITxOnlineAuthenticatedMenu: FC<{ className?: string }> = ({ + className, +}) => ( + + + + + + +); + +// --------------------------------------------------------------------------- +// Always-desktop header layout. The frontend-base shell swaps to a hamburger + +// centered-logo MobileLayout below 768px (via a JS media query). MIT OL keeps +// the desktop-style layout (logo left, course info, user menu right) at every +// width to match the rest of the platform, so we replace the shell's +// DesktopLayout with one that never applies `d-none` and replace MobileLayout +// with nothing. Narrow-width trimming is handled in the deployment SCSS. +// --------------------------------------------------------------------------- + +const AlwaysDesktopLayout: FC = () => ( + +
+ +
+
+ +
+
+); + +const NoMobileLayout: FC = () => null; + // --------------------------------------------------------------------------- // MITx Online header — full UAI/Learn course detection, custom logo, user menu // --------------------------------------------------------------------------- @@ -168,6 +221,21 @@ const MITxOnlineLogoutMenuItem: FC = () => { export function createMITxOnlineHeaderApp(): App { const slots: SlotOperation[] = [ + // Keep the desktop-style header layout at all widths (no mobile hamburger). + { + slotId: SLOT.desktop, + id: "mitol.header.mitxonline.desktopLayout", + relatedId: WIDGET.desktopLayout, + op: WidgetOperationTypes.REPLACE, + component: AlwaysDesktopLayout, + }, + { + slotId: SLOT.mobile, + id: "mitol.header.mitxonline.mobileLayout", + relatedId: WIDGET.mobileLayout, + op: WidgetOperationTypes.REPLACE, + component: NoMobileLayout, + }, // Replace desktop and mobile logo widgets with context-aware logo. { slotId: SLOT.desktopLeft, @@ -190,6 +258,28 @@ export function createMITxOnlineHeaderApp(): App { op: WidgetOperationTypes.PREPEND, component: MITxOnlineDashboardLink, }, + // Remove the default Help link from the header. + { + slotId: SLOT.secondaryLinks, + op: WidgetOperationTypes.REMOVE, + relatedId: WIDGET.help, + }, + // Replace the default avatar-button toggle with the custom MIT OL user menu + // (person icon + display name + chevron), on both desktop and mobile. + { + slotId: SLOT.desktopRight, + id: "mitol.header.mitxonline.desktopAuthenticatedMenu", + relatedId: WIDGET.desktopAuthenticatedMenu, + op: WidgetOperationTypes.REPLACE, + component: MITxOnlineAuthenticatedMenu, + }, + { + slotId: SLOT.mobileRight, + id: "mitol.header.mitxonline.mobileAuthenticatedMenu", + relatedId: WIDGET.mobileAuthenticatedMenu, + op: WidgetOperationTypes.REPLACE, + component: MITxOnlineAuthenticatedMenu, + }, // Replace all three default authenticated menu items with mitxonline-specific ones. { slotId: SLOT.authenticatedMenu, @@ -230,12 +320,6 @@ export function createMITxOnlineHeaderApp(): App { op: WidgetOperationTypes.APPEND, component: MITxOnlineLogoutMenuItem, }, - // TODO: Replace AuthenticatedMenu widget toggle with UserMenuToggle for custom SVG icon. - // Requires REPLACE on org.openedx.frontend.widget.header.desktopAuthenticatedMenu.v1 - // and org.openedx.frontend.widget.header.mobileAuthenticatedMenu.v1 — but these widgets - // embed both the toggle and the menu, so a full custom AuthenticatedMenu component is - // needed. Deferred until confirmed approach with UX. - // // TODO: Hide primary nav links on dashboard apps (gradebook, learner-dashboard). // This requires knowing which route roles those apps register. Add a condition with // condition: { active: [''] } once frontend-app-gradebook is a module. @@ -392,6 +476,3 @@ export function createXProHeaderApp(): App { return { appId: "mitol.header.xpro", slots }; } - -// Re-export for consumers that only need the shared toggle component. -export { UserMenuToggle }; diff --git a/deployments/mit-ol/mfe_slot_config/frontend/shared/src/styles/mitxonline.scss b/deployments/mit-ol/mfe_slot_config/frontend/shared/src/styles/mitxonline.scss index 9e039a18..a9f16107 100644 --- a/deployments/mit-ol/mfe_slot_config/frontend/shared/src/styles/mitxonline.scss +++ b/deployments/mit-ol/mfe_slot_config/frontend/shared/src/styles/mitxonline.scss @@ -1,15 +1,23 @@ -.learning-header, .site-header-desktop { +.learning-header, .site-header-desktop, header.border-bottom { background-color: #303337 !important; color: #FFF !important; font-family: neue-haas-grotesk-text, sans-serif !important; font-style: normal !important; font-size: 14px !important; line-height: 18px !important; + // Remove the shell's light-grey bottom border. The `.border-bottom` utility + // lives in the paragon cascade layer with !important, so a site-layer + // !important override cannot win; instead we zero the CSS variable it + // resolves (--pgn-size-border-width), which removes the line without fighting + // the cascade. Children with explicit borders (dropdown toggle/menu) are + // unaffected because they set their own border values. + --pgn-size-border-width: 0; + box-shadow: none !important; .user-dropdown .btn { height: 40px !important; } - .container-xl{ + .container-xl, .container-mw-xl { padding: 16px 8px !important; } @@ -21,6 +29,11 @@ height: 24px !important; } + // Spacing between the Dashboard button group and the user menu dropdown. + .secondary-nav-links { + margin-right: 16px !important; + } + .font-weight-bold { font-weight: 400 !important; } @@ -47,7 +60,8 @@ font-size: 0.875rem !important; line-height: 1.125rem !important; opacity: 0.75 !important; - height: auto !important; + // Match the Dashboard button height so the two sit at the same size. + height: 40px !important; &:hover, &:focus { display: flex !important; @@ -172,9 +186,11 @@ .btn:not(:disabled, .disabled, .btn-link).focus::before, .btn:not(:disabled, .disabled, .btn-link):focus::before { border: none !important; } -// Mobile responsive adjustments +// Mobile responsive adjustments. The custom header keeps the desktop-style +// layout at all widths (see AlwaysDesktopLayout in shared/src/header); these +// rules trim it for narrow viewports. @media (max-width: 991px) { - .learning-header, .site-header-desktop { + .learning-header, .site-header-desktop, header.border-bottom { // Hide standalone dashboard button; moved into user dropdown .dashboard-btn { display: none !important; } diff --git a/deployments/mit-ol/mfe_slot_config/frontend/xpro/public/index.html b/deployments/mit-ol/mfe_slot_config/frontend/xpro/public/index.html index 456801d8..3b9c4915 100644 --- a/deployments/mit-ol/mfe_slot_config/frontend/xpro/public/index.html +++ b/deployments/mit-ol/mfe_slot_config/frontend/xpro/public/index.html @@ -1,6 +1,8 @@ + + MIT xPRO From 1461a11f8c31ca071d45922eec95542bd642b3b0 Mon Sep 17 00:00:00 2001 From: Muhammad Anas Date: Mon, 6 Jul 2026 16:00:49 +0500 Subject: [PATCH 2/5] fix: alignment issues --- .../frontend/shared/src/footer/index.tsx | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/deployments/mit-ol/mfe_slot_config/frontend/shared/src/footer/index.tsx b/deployments/mit-ol/mfe_slot_config/frontend/shared/src/footer/index.tsx index d4647525..ebe0f76b 100644 --- a/deployments/mit-ol/mfe_slot_config/frontend/shared/src/footer/index.tsx +++ b/deployments/mit-ol/mfe_slot_config/frontend/shared/src/footer/index.tsx @@ -92,14 +92,34 @@ function PoweredBy() { function MITOLDesktopFooterLayout() { return (