diff --git a/package-lock.json b/package-lock.json index ca6d74d6bb..a94b47ba3a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@edx/brand": "npm:@openedx/brand-openedx@^1.2.2", "@edx/browserslist-config": "1.2.0", "@edx/frontend-component-header": "^5.6.0", - "@edx/frontend-lib-learning-assistant": "^2.2.4", + "@edx/frontend-lib-learning-assistant": "^2.4.1", "@edx/frontend-lib-special-exams": "^3.1.3", "@edx/frontend-platform": "^8.0.0", "@edx/openedx-atlas": "^0.6.0", @@ -2438,9 +2438,9 @@ } }, "node_modules/@edx/frontend-lib-learning-assistant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/@edx/frontend-lib-learning-assistant/-/frontend-lib-learning-assistant-2.2.4.tgz", - "integrity": "sha512-EvN22XhDzzdIYrJY1TKEF/5zXuozC4tlKRo96+dSe9I7UAMg4zsLKC1zqY15oUuyB8rHo1ERYhQBG18uMepXrw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@edx/frontend-lib-learning-assistant/-/frontend-lib-learning-assistant-2.4.1.tgz", + "integrity": "sha512-sq0kS7vE7FKjHFSwAz77nz/E0PmmS0e/AxLGtcit+9xNyYU6b5SrJA8BmH5C7tgvtaeGJER54w0ZTkndPAo5eA==", "dependencies": { "@edx/brand": "npm:@edx/brand-openedx@1.2.0", "@fortawesome/fontawesome-svg-core": "1.2.36", @@ -2463,7 +2463,7 @@ "react-redux": "7.2.9", "react-router": "5.2.1 || ^6.0.0", "react-router-dom": "5.3.0 || ^6.0.0", - "redux": "4.1.2", + "redux": "^4", "regenerator-runtime": "0.13.11" } }, @@ -7275,9 +7275,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001673", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001673.tgz", - "integrity": "sha512-WTrjUCSMp3LYX0nE12ECkV0a+e6LC85E0Auz75555/qr78Oc8YWhEPNfDd6SHdtlCMSzqtuXY0uyEMNRcsKpKw==", + "version": "1.0.30001680", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001680.tgz", + "integrity": "sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==", "funding": [ { "type": "opencollective", diff --git a/package.json b/package.json index e0a33bf98b..08af9a14d5 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "@edx/brand": "npm:@openedx/brand-openedx@^1.2.2", "@edx/browserslist-config": "1.2.0", "@edx/frontend-component-header": "^5.6.0", - "@edx/frontend-lib-learning-assistant": "^2.2.4", + "@edx/frontend-lib-learning-assistant": "^2.4.1", "@edx/frontend-lib-special-exams": "^3.1.3", "@edx/frontend-platform": "^8.0.0", "@edx/openedx-atlas": "^0.6.0", diff --git a/src/courseware/course/sequence/Unit/ContentIFrame.jsx b/src/courseware/course/sequence/Unit/ContentIFrame.jsx index 54c4d6620f..7f99e4c172 100644 --- a/src/courseware/course/sequence/Unit/ContentIFrame.jsx +++ b/src/courseware/course/sequence/Unit/ContentIFrame.jsx @@ -20,7 +20,7 @@ import * as hooks from './hooks'; * Changes to it should be vetted by them (security@edx.org). */ export const IFRAME_FEATURE_POLICY = ( - 'microphone *; camera *; midi *; geolocation *; encrypted-media *, clipboard-write *' + 'microphone *; camera *; midi *; geolocation *; encrypted-media *; clipboard-write *' ); export const testIDs = StrictDict({ diff --git a/src/generic/PageNotFound.jsx b/src/generic/PageNotFound.jsx new file mode 100644 index 0000000000..44c2c46629 --- /dev/null +++ b/src/generic/PageNotFound.jsx @@ -0,0 +1,49 @@ +import { getConfig } from '@edx/frontend-platform'; +import { Hyperlink } from '@openedx/paragon'; +import { useIntl } from '@edx/frontend-platform/i18n'; +import { logError } from '@edx/frontend-platform/logging'; +import { sendTrackEvent } from '@edx/frontend-platform/analytics'; +import FooterSlot from '@openedx/frontend-slot-footer'; + +import HeaderSlot from '../plugin-slots/HeaderSlot'; +import messages from './messages'; + +const PageNotFound = () => { + const { formatMessage } = useIntl(); + const location = window.location.href; + + logError('Page failed to load, probably an invalid URL.', location); + sendTrackEvent('edx.ui.lms.page_not_found', { location }); + + return ( + <> + +
+

+ {formatMessage(messages.pageNotFoundHeader)} +

+

+ {formatMessage( + messages.pageNotFoundBody, + { + homepageLink: ( + + {formatMessage(messages.homepageLink)} + + ), + }, + )} +

+
+ + + ); +}; + +export default PageNotFound; diff --git a/src/generic/PageNotFound.test.jsx b/src/generic/PageNotFound.test.jsx new file mode 100644 index 0000000000..86aa854f24 --- /dev/null +++ b/src/generic/PageNotFound.test.jsx @@ -0,0 +1,41 @@ +import { getConfig, history } from '@edx/frontend-platform'; +import { Routes, Route } from 'react-router-dom'; +import { sendTrackEvent } from '@edx/frontend-platform/analytics'; + +import { + initializeTestStore, + render, + screen, +} from '../setupTest'; +import PageNotFound from './PageNotFound'; +import messages from './messages'; + +jest.mock('@edx/frontend-platform/analytics'); + +describe('PageNotFound', () => { + beforeEach(async () => { + await initializeTestStore(); + const invalidUrl = '/new/course'; + history.push(invalidUrl); + render( + + } /> + , + { wrapWithRouter: true }, + ); + }); + + it('displays page not found header', () => { + expect(screen.getByText(messages.pageNotFoundHeader.defaultMessage)).toBeVisible(); + }); + + it('displays link back to learner dashboard', () => { + const expected = getConfig().LMS_BASE_URL; + const homepageLink = screen.getByRole('link', { name: messages.homepageLink.defaultMessage }); + expect(homepageLink).toHaveAttribute('href', expected); + }); + + it('calls tracking events', () => { + expect(sendTrackEvent).toHaveBeenCalled(); + }); +}); diff --git a/src/generic/messages.ts b/src/generic/messages.ts index 816378ad73..020ab81c67 100644 --- a/src/generic/messages.ts +++ b/src/generic/messages.ts @@ -21,6 +21,21 @@ const messages = defineMessages({ defaultMessage: 'Sign in', description: 'Text in a button, prompting the user to log in.', }, + pageNotFoundHeader: { + id: 'learning.pageNotFound.header', + defaultMessage: 'Page not found', + description: 'Text for header notifying them that the page is not found', + }, + pageNotFoundBody: { + id: 'learning.pageNotFound.body', + defaultMessage: 'The page you you were looking for was not found. Go back to the {homepageLink}.', + description: 'Text for body, prompting the user to go back to the home page', + }, + homepageLink: { + id: 'learning.pageNotFound.body.homepageLink.label', + defaultMessage: 'homepage', + description: 'Text for url, telling them the page they will be navigated to', + }, }); export default messages; diff --git a/src/index.jsx b/src/index.jsx index ec4f4dc284..af7a153096 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -4,7 +4,6 @@ import { getConfig, } from '@edx/frontend-platform'; import { AppProvider, ErrorPage, PageWrap } from '@edx/frontend-platform/react'; -import React from 'react'; import ReactDOM from 'react-dom'; import { Routes, Route } from 'react-router-dom'; @@ -35,6 +34,7 @@ import CourseAccessErrorPage from './generic/CourseAccessErrorPage'; import DecodePageRoute from './decode-page-route'; import { DECODE_ROUTES, ROUTES } from './constants'; import PreferencesUnsubscribe from './preferences-unsubscribe'; +import PageNotFound from './generic/PageNotFound'; subscribe(APP_READY, () => { ReactDOM.render( @@ -46,6 +46,7 @@ subscribe(APP_READY, () => { + } /> } /> } /> } /> diff --git a/src/tab-page/LoadedTabPage.jsx b/src/tab-page/LoadedTabPage.jsx index 0259aa1bfd..938d95cefd 100644 --- a/src/tab-page/LoadedTabPage.jsx +++ b/src/tab-page/LoadedTabPage.jsx @@ -69,7 +69,7 @@ const LoadedTabPage = ({ streakDiscountCouponEnabled={streakDiscountCouponEnabled} verifiedMode={verifiedMode} /> -
+
-
+
{children}