From a6dad89ae91badb83e6eead7df45701932c2e77c Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 1 Nov 2023 12:15:06 -0400 Subject: [PATCH] fix: use react-router to properly handle basepaths for internal links --- package-lock.json | 3 +- package.json | 3 +- src/studio-header/CourseLockUp.jsx | 43 ++++++++++++++----------- src/studio-header/MobileMenu.jsx | 12 +++++-- src/studio-header/NavDropdownMenu.jsx | 42 +++++++++++++++++++----- src/studio-header/StudioHeader.test.jsx | 21 ++++++------ src/studio-header/utils.js | 4 +-- 7 files changed, 86 insertions(+), 42 deletions(-) diff --git a/package-lock.json b/package-lock.json index a743a98ee..03e984e81 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,7 +47,8 @@ "@edx/frontend-platform": "^4.0.0 || ^5.0.0 || ^6.0.0", "prop-types": "^15.5.10", "react": "^16.9.0 || ^17.0.0", - "react-dom": "^16.9.0 || ^17.0.0" + "react-dom": "^16.9.0 || ^17.0.0", + "react-router-dom": "^6.14.2" } }, "node_modules/@aashutoshrathi/word-wrap": { diff --git a/package.json b/package.json index f0867b37f..f902097c1 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "@edx/frontend-platform": "^4.0.0 || ^5.0.0 || ^6.0.0", "prop-types": "^15.5.10", "react": "^16.9.0 || ^17.0.0", - "react-dom": "^16.9.0 || ^17.0.0" + "react-dom": "^16.9.0 || ^17.0.0", + "react-router-dom": "^6.14.2" } } diff --git a/src/studio-header/CourseLockUp.jsx b/src/studio-header/CourseLockUp.jsx index d4946c93d..4b3282321 100644 --- a/src/studio-header/CourseLockUp.jsx +++ b/src/studio-header/CourseLockUp.jsx @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { useNavigate, useResolvedPath } from 'react-router-dom'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { OverlayTrigger, @@ -14,26 +15,32 @@ const CourseLockUp = ({ title, // injected intl, -}) => ( - - {title} - +}) => { + const navigate = useNavigate(); + const resolvedPath = useResolvedPath(outlineLink); + + return ( + + {title} + )} - > - - {org} {number} - {title} - - -); + { e.preventDefault(); navigate(resolvedPath.pathname); }} + aria-label={intl.formatMessage(messages['header.label.courseOutline'])} + data-testid="course-lock-up-block" + > + {org} {number} + {title} + + + ); +}; CourseLockUp.propTypes = { number: PropTypes.string, diff --git a/src/studio-header/MobileMenu.jsx b/src/studio-header/MobileMenu.jsx index 3ff31e6c1..2bfd899a0 100644 --- a/src/studio-header/MobileMenu.jsx +++ b/src/studio-header/MobileMenu.jsx @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { NavLink } from 'react-router-dom'; import { Collapsible } from '@edx/paragon'; const MobileMenu = ({ @@ -21,9 +22,14 @@ const MobileMenu = ({ diff --git a/src/studio-header/NavDropdownMenu.jsx b/src/studio-header/NavDropdownMenu.jsx index 4cacf3adb..80a56b885 100644 --- a/src/studio-header/NavDropdownMenu.jsx +++ b/src/studio-header/NavDropdownMenu.jsx @@ -1,10 +1,34 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { useNavigate, useResolvedPath } from 'react-router-dom'; + import { Dropdown, DropdownButton, } from '@edx/paragon'; +const NavDropdownItem = ({ item }) => { + const navigate = useNavigate(); + const resolvedPath = useResolvedPath(item.href); + + return ( + { e.preventDefault(); navigate(resolvedPath.pathname); }} + className="small" + > + {item.title} + + ); +}; + +NavDropdownItem.propTypes = { + item: PropTypes.shape({ + href: PropTypes.string, + title: PropTypes.string, + }).isRequired, +}; + const NavDropdownMenu = ({ id, buttonTitle, @@ -15,14 +39,16 @@ const NavDropdownMenu = ({ title={buttonTitle} variant="tertiary" > - {items.map(item => ( - - {item.title} - - ))} + {items.map(item => (/^(?:\w+:)?\/\//.test(item.href) + ? ( + + {item.title} + + ) + : ))} ); diff --git a/src/studio-header/StudioHeader.test.jsx b/src/studio-header/StudioHeader.test.jsx index 8ebda05cd..961c98e80 100644 --- a/src/studio-header/StudioHeader.test.jsx +++ b/src/studio-header/StudioHeader.test.jsx @@ -5,6 +5,7 @@ import { fireEvent, waitFor, } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; import { AppContext } from '@edx/frontend-platform/react'; import { IntlProvider } from '@edx/frontend-platform/i18n'; @@ -40,15 +41,17 @@ const RootWrapper = ({ return ( // eslint-disable-next-line react/jsx-no-constructed-context-values, react/prop-types - - - - - - - + + + + + + + + + ); }; diff --git a/src/studio-header/utils.js b/src/studio-header/utils.js index c4b36589c..f734c65c7 100644 --- a/src/studio-header/utils.js +++ b/src/studio-header/utils.js @@ -8,7 +8,7 @@ const getUserMenuItems = ({ }) => { let items = [ { - href: `${studioBaseUrl}}`, + href: `${studioBaseUrl}`, title: intl.formatMessage(messages['header.user.menu.studio']), }, { href: `${logoutUrl}`, @@ -18,7 +18,7 @@ const getUserMenuItems = ({ if (isAdmin) { items = [ { - href: `${studioBaseUrl}}`, + href: `${studioBaseUrl}`, title: intl.formatMessage(messages['header.user.menu.studio']), }, { href: `${studioBaseUrl}/maintenance`,