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, () => { + } /> } /> } /> } />