Skip to content
Open
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
},
"scripts": {
"build": "make build",
"prepare": "make build",
"i18n_extract": "fedx-scripts formatjs extract",
"lint": "fedx-scripts eslint --ext .js --ext .jsx .",
"lint:fix": "fedx-scripts eslint --fix --ext .js --ext .jsx .",
Expand Down
18 changes: 17 additions & 1 deletion src/_footer.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
$gray-footer: #fcfcfc !default;

.footer {
.site-footer {
background-color: var(--pgn-color-light-100, $gray-footer);

&__link {
color: var(--pgn-color-gray-700, #454545);
font-weight: 500;
text-decoration: none;

&:hover,
&:focus {
color: var(--pgn-color-primary-500, #0a3055);
text-decoration: underline;
}
}

&__legal p {
color: var(--pgn-color-gray-500, #6b7280);
}
}
97 changes: 75 additions & 22 deletions src/components/Footer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,34 @@ import { AppContext } from '@edx/frontend-platform/react';

import messages from './Footer.messages';
import LanguageSelector from './LanguageSelector';
import OpsWerksLogo from './assets/opswerks.png'

ensureConfig([
'LMS_BASE_URL',
'LOGO_TRADEMARK_URL',
'LOGO_URL',
], 'Footer component');

const EVENT_NAMES = {
FOOTER_LINK: 'edx.bi.footer.link',
};

// Falls back to these if FOOTER_NAV_LINKS isn't set via MFE_CONFIG
// (see plugins/openlms_brand.py).
const DEFAULT_FOOTER_LINKS = [
{ title: 'Dashboard', url: '/dashboard' },
{ title: 'Discover', url: '/courses' },
{ title: 'Help Center', url: '/help' },
];

// "ACCOUNT_PROFILE_URL" is a magic value config-side links can use in place
// of a static path, since "my own profile" has no fixed relative URL.
const resolveHref = (url, config) => {
if (url === 'ACCOUNT_PROFILE_URL') {
return config.ACCOUNT_PROFILE_URL;
}
return url.startsWith('http') ? url : `${config.LMS_BASE_URL}${url}`;
};

const SiteFooter = ({
supportedLanguages,
onLanguageSelected,
Expand All @@ -26,6 +44,8 @@ const SiteFooter = ({
const { config } = useContext(AppContext);

const showLanguageSelector = supportedLanguages.length > 0 && onLanguageSelected;
const footerLinks = config.FOOTER_NAV_LINKS || DEFAULT_FOOTER_LINKS;
const companyName = config.FOOTER_COMPANY_NAME || config.SITE_NAME;

const externalLinkClickHandler = (event) => {
const label = event.currentTarget.getAttribute('href');
Expand All @@ -40,28 +60,61 @@ const SiteFooter = ({
return (
<footer
role="contentinfo"
className="footer d-flex border-top py-3 px-4"
aria-label={intl.formatMessage(messages['footer.ariaLabel'])}
className="site-footer border-top py-3"
>
<div className="container-fluid d-flex">
<a
className="d-block"
href={config.LMS_BASE_URL}
aria-label={intl.formatMessage(messages['footer.logo.ariaLabel'])}
onClick={externalLinkClickHandler}
>
<img
style={{ maxHeight: 45 }}
src={logo || config.LOGO_TRADEMARK_URL}
alt={intl.formatMessage(messages['footer.logo.altText'])}
/>
</a>
<div className="flex-grow-1" />
{showLanguageSelector && (
<LanguageSelector
options={supportedLanguages}
onSubmit={onLanguageSelected}
/>
)}
<div className="container-fluid">
<div className="d-flex justify-content-between align-items-center py-4">
<div >
<a
className="d-block mb-4"
href={config.LMS_BASE_URL}
aria-label={intl.formatMessage(messages['footer.logo.ariaLabel'])}
onClick={externalLinkClickHandler}
>
<img
style={{ maxHeight: 45 }}
src={OpsWerksLogo}
alt={intl.formatMessage(messages['footer.logo.altText'])}
/>
</a>
<p className="small text-gray-500 mb-0">
{intl.formatMessage(messages['footer.legal.operatedBy'], {
siteName: config.SITE_NAME,
companyName,
})}
</p>
<p className="small text-gray-500 mb-0">
{intl.formatMessage(messages['footer.legal.copyright'], {
year: 2026,
companyName,
})}
</p>

</div>
<nav
className="site-footer__links d-flex flex-wrap align-items-center"
aria-label={intl.formatMessage(messages['footer.nav.ariaLabel'])}
>
{footerLinks.map(({ title, url }) => (
<a
key={title}
className="text-black ml-4"
style={{ textDecoration: 'none' , color: 'black', fontWeight: 300, fontSize: 14, lineHeight: 1.5 }}
href={resolveHref(url, config)}
onClick={externalLinkClickHandler}
>
{title}
</a>
))}
{showLanguageSelector && (
<LanguageSelector
options={supportedLanguages}
onSubmit={onLanguageSelected}
/>
)}
</nav>
</div>
</div>
</footer>
);
Expand Down
15 changes: 15 additions & 0 deletions src/components/Footer.messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,21 @@ const messages = defineMessages({
defaultMessage: 'Page Footer',
description: 'aria-label for the footer component',
},
'footer.nav.ariaLabel': {
id: 'footer.nav.ariaLabel',
defaultMessage: 'Footer',
description: 'aria-label for the footer navigation links region.',
},
'footer.legal.operatedBy': {
id: 'footer.legal.operatedBy',
defaultMessage: '{siteName} is operated and maintained by {companyName}.',
description: 'Text stating which company operates and maintains the site, shown in the footer.',
},
'footer.legal.copyright': {
id: 'footer.legal.copyright',
defaultMessage: '© {year} {companyName}. All rights reserved.',
description: 'Copyright notice shown in the footer.',
},
});

export default messages;
Binary file added src/components/assets/opswerks.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.