diff --git a/package.json b/package.json index c32f6c8fcd..2cba4f0071 100644 --- a/package.json +++ b/package.json @@ -89,8 +89,8 @@ "react-markdown": "^5.0.3", "react-picky": "^5.3.2", "react-redux": "^5.0.5", - "react-router": "^4.1.1", - "react-router-dom": "^4.1.1", + "react-router": "^7.0.0", + "react-router-dom": "^7.0.0", "react-select": "^3.0.4", "react-test-renderer": "^16.8.4", "react-zendesk": "^0.1.5", diff --git a/static/js/Router.js b/static/js/Router.js index c4f6861ec3..ee4583de76 100644 --- a/static/js/Router.js +++ b/static/js/Router.js @@ -1,30 +1,31 @@ import React from "react" -import { Route, Router as ReactRouter } from "react-router" +import { BrowserRouter, Route } from "react-router-dom" import { Provider } from "react-redux" import App from "./containers/App" import withTracker from "./util/withTracker" import ScrollToTop from "./components/ScrollToTop" +const TrackedApp = withTracker(App); + export default class Root extends React.Component { props: { - history: Object, store: Store } render() { - const { children, history, store } = this.props + const { children, store } = this.props return (
- + {children} - +
) } } -export const routes = +export const routes = } /> diff --git a/static/js/components/PrivateRoute.js b/static/js/components/PrivateRoute.js index f94b696717..6bafdb4693 100644 --- a/static/js/components/PrivateRoute.js +++ b/static/js/components/PrivateRoute.js @@ -1,6 +1,6 @@ // @flow import React, { type ComponentType } from "react"; -import { Route, Redirect } from "react-router-dom"; +import { Route, Navigate } from "react-router-dom"; import { connect } from "react-redux"; import { compose } from "redux"; import { createStructuredSelector } from "reselect"; @@ -28,7 +28,7 @@ export const PrivateRoute = ({ return currentUser && currentUser.is_authenticated ? ( ) : ( - + ); }} /> diff --git a/static/js/components/PrivateRoute_test.js b/static/js/components/PrivateRoute_test.js index 9e0509d31e..b1004b659e 100644 --- a/static/js/components/PrivateRoute_test.js +++ b/static/js/components/PrivateRoute_test.js @@ -1,7 +1,7 @@ // @flow import React from "react"; import { assert } from "chai"; -import { Redirect } from "react-router-dom"; +import { Navigate } from "react-router-dom"; import PrivateRoute, { PrivateRoute as InnerPrivateRoute, @@ -56,7 +56,7 @@ describe("PrivateRoute component", () => { assert.equal(path, fakeRoutePath); const renderResult = render(); if (isAnonymous) { - assert.equal(renderResult.type, Redirect); + assert.equal(renderResult.type, Navigate); assert.equal( renderResult.props.to, `${routes.login.begin}?next=%2Fprotected%2Froute`, diff --git a/static/js/components/ScrollToTop.js b/static/js/components/ScrollToTop.js index e977b48959..4d9fdf06e1 100644 --- a/static/js/components/ScrollToTop.js +++ b/static/js/components/ScrollToTop.js @@ -1,16 +1,12 @@ -import React from "react"; -import { withRouter } from "react-router"; +import React, { useEffect } from "react"; +import { useLocation } from "react-router-dom"; -class ScrollToTop extends React.Component { - componentDidUpdate(prevProps) { - if (this.props.location.pathname !== prevProps.location.pathname) { - window.scrollTo(0, 0); - } - } - - render() { - return this.props.children; - } +function ScrollToTop({ children }) { + const location = useLocation(); + useEffect(() => { + window.scrollTo(0, 0); + }, [location.pathname]); + return children; } -export default withRouter(ScrollToTop); +export default ScrollToTop; diff --git a/static/js/containers/App.js b/static/js/containers/App.js index 57f805c883..fa57d2b981 100644 --- a/static/js/containers/App.js +++ b/static/js/containers/App.js @@ -3,7 +3,7 @@ import React from "react"; import { compose } from "redux"; import { connect } from "react-redux"; -import { Switch, Route } from "react-router"; +import { Routes, Route } from "react-router-dom"; import { connectRequest } from "redux-query"; import { createStructuredSelector } from "reselect"; import urljoin from "url-join"; @@ -84,49 +84,76 @@ export class App extends React.Component { errorPageHeader={null} courseTopics={courseTopics} /> - - + + + + } /> - + + + } /> } /> } /> - + + + } /> + + + } /> + + + } /> + + + } /> - + + + } /> } /> - + ); } diff --git a/static/js/containers/pages/admin/EcommerceAdminPages.js b/static/js/containers/pages/admin/EcommerceAdminPages.js index 5dadaa172d..cb52594a46 100644 --- a/static/js/containers/pages/admin/EcommerceAdminPages.js +++ b/static/js/containers/pages/admin/EcommerceAdminPages.js @@ -5,7 +5,7 @@ declare var USER_PERMISSIONS: { has_coupon_product_assignment_permission: boolean, }; import React from "react"; -import { Redirect, Route, Switch, Link } from "react-router-dom"; +import { Navigate, Route, Routes, Link } from "react-router-dom"; import { routes } from "../../../lib/urls"; @@ -50,34 +50,34 @@ const EcommerceAdminIndexPage = () => ( const EcommerceAdminPages = () => ( - + } /> } /> } /> } /> } /> - - + + ); diff --git a/static/js/containers/pages/b2b/EcommerceBulkPages.js b/static/js/containers/pages/b2b/EcommerceBulkPages.js index 680bf7de1a..6c9cd25bd7 100644 --- a/static/js/containers/pages/b2b/EcommerceBulkPages.js +++ b/static/js/containers/pages/b2b/EcommerceBulkPages.js @@ -1,6 +1,6 @@ // @flow import React from "react"; -import { Route, Switch } from "react-router-dom"; +import { Routes, Route } from "react-router-dom"; import { routes } from "../../../lib/urls"; @@ -9,18 +9,10 @@ import B2BReceiptPage from "./B2BReceiptPage"; const EcommerceBulkPages = () => ( - - - - + + } /> + } /> + ); diff --git a/static/js/containers/pages/login/LoginPages.js b/static/js/containers/pages/login/LoginPages.js index 98e95a4395..cfb4faf136 100644 --- a/static/js/containers/pages/login/LoginPages.js +++ b/static/js/containers/pages/login/LoginPages.js @@ -1,6 +1,6 @@ // @flow import React from "react"; -import { Switch, Route } from "react-router-dom"; +import { Routes, Route } from "react-router-dom"; import { routes } from "../../../lib/urls"; @@ -14,25 +14,31 @@ const ForgotPasswordPages = () => ( } /> } + /> + } /> ); const LoginPages = () => ( - - - + + } /> + } /> } /> - + ); export default LoginPages; diff --git a/static/js/containers/pages/profile/ProfilePages.js b/static/js/containers/pages/profile/ProfilePages.js index 77e47719f5..4c4207b61d 100644 --- a/static/js/containers/pages/profile/ProfilePages.js +++ b/static/js/containers/pages/profile/ProfilePages.js @@ -1,6 +1,6 @@ // @flow import React from "react"; -import { Switch, Route } from "react-router-dom"; +import { Routes, Route } from "react-router-dom"; import { routes } from "../../../lib/urls"; @@ -8,10 +8,10 @@ import ViewProfilePage from "./ViewProfilePage"; import EditProfilePage from "./EditProfilePage"; const ProfilePages = () => ( - - - - + + } /> + } /> + ); export default ProfilePages; diff --git a/static/js/containers/pages/register/RegisterConfirmPage_test.js b/static/js/containers/pages/register/RegisterConfirmPage_test.js index add91c9d68..ffec6a7d6c 100644 --- a/static/js/containers/pages/register/RegisterConfirmPage_test.js +++ b/static/js/containers/pages/register/RegisterConfirmPage_test.js @@ -82,10 +82,14 @@ describe("RegisterConfirmPage", () => { }); const confirmationErrorText = inner.find(".confirmation-message"); assert.isNotNull(confirmationErrorText); - assert.equal( - confirmationErrorText.text().replace("", ""), - "This invitation is invalid or has expired. Please .", + assert.include( + confirmationErrorText.text(), + "This invitation is invalid or has expired. Please", ); + const link = confirmationErrorText.find("Link"); + assert.isNotNull(link); + assert.equal(link.prop("to"), routes.register); + assert.equal(link.text(), "click here to register again"); }); it("Shows a login link with existing account message", async () => { @@ -102,10 +106,14 @@ describe("RegisterConfirmPage", () => { }); const confirmationErrorText = inner.find(".confirmation-message"); assert.isNotNull(confirmationErrorText); - assert.equal( - confirmationErrorText.text().replace("", ""), - "You already have an xPRO account. Please .", + assert.include( + confirmationErrorText.text(), + "You already have an xPRO account. Please", ); + const link = confirmationErrorText.find("Link"); + assert.isNotNull(link); + assert.equal(link.prop("to"), routes.login); + assert.equal(link.text(), "click here to sign in"); }); it("Shows a register link with invalid or no confirmation code", async () => { @@ -121,9 +129,14 @@ describe("RegisterConfirmPage", () => { }, }); const confirmationErrorText = inner.find(".confirmation-message"); - assert.equal( - confirmationErrorText.text().replace("", ""), - "No confirmation code was provided or it has expired. .", + assert.isNotNull(confirmationErrorText); + assert.include( + confirmationErrorText.text(), + "No confirmation code was provided or it has expired.", ); + const link = confirmationErrorText.find("Link"); + assert.isNotNull(link); + assert.equal(link.prop("to"), routes.register); + assert.equal(link.text(), "click here to register again"); }); }); diff --git a/static/js/containers/pages/register/RegisterDetailsPage_test.js b/static/js/containers/pages/register/RegisterDetailsPage_test.js index ab42465f6a..db32545f6d 100644 --- a/static/js/containers/pages/register/RegisterDetailsPage_test.js +++ b/static/js/containers/pages/register/RegisterDetailsPage_test.js @@ -164,9 +164,13 @@ describe("RegisterDetailsPage", () => { }); const confirmationMessage = inner.find(".confirmation-message"); assert.isNotNull(confirmationMessage); - assert.equal( - confirmationMessage.text().replace("", ""), - "You already have an xPRO account. Please .", + assert.include( + confirmationMessage.text(), + "You already have an xPRO account. Please", ); + const link = confirmationMessage.find("Link"); + assert.isNotNull(link); + assert.equal(link.prop("to"), routes.login); + assert.equal(link.text(), "click here to sign in"); }); }); diff --git a/static/js/containers/pages/register/RegisterPages.js b/static/js/containers/pages/register/RegisterPages.js index 4bac48d260..00f495c0ba 100644 --- a/static/js/containers/pages/register/RegisterPages.js +++ b/static/js/containers/pages/register/RegisterPages.js @@ -1,6 +1,6 @@ // @flow import React from "react"; -import { Switch, Route, Redirect } from "react-router-dom"; +import { Routes, Route, Navigate } from "react-router-dom"; import { routes } from "../../../lib/urls"; @@ -14,36 +14,44 @@ import RegisterErrorPage from "./RegisterErrorPage"; const RegisterPages = () => ( - - + + } + /> } /> } /> } /> } + /> + } /> - } /> - - + + ); diff --git a/yarn.lock b/yarn.lock index 5b004f8784..fb2e4431ef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4200,6 +4200,13 @@ __metadata: languageName: node linkType: hard +"cookie@npm:^1.0.1": + version: 1.0.2 + resolution: "cookie@npm:1.0.2" + checksum: 2c5a6214147ffa7135ce41860c781de17e93128689b0d080d3116468274b3593b607bcd462ac210d3a61f081db3d3b09ae106e18d60b1f529580e95cf2db8a55 + languageName: node + linkType: hard + "cookiejar@npm:^2.1.0": version: 2.1.4 resolution: "cookiejar@npm:2.1.4" @@ -6790,20 +6797,6 @@ __metadata: languageName: node linkType: hard -"history@npm:^4.7.2": - version: 4.10.1 - resolution: "history@npm:4.10.1" - dependencies: - "@babel/runtime": ^7.1.2 - loose-envify: ^1.2.0 - resolve-pathname: ^3.0.0 - tiny-invariant: ^1.0.2 - tiny-warning: ^1.0.0 - value-equal: ^1.0.1 - checksum: addd84bc4683929bae4400419b5af132ff4e4e9b311a0d4e224579ea8e184a6b80d7f72c55927e4fa117f69076a9e47ce082d8d0b422f1a9ddac7991490ca1d0 - languageName: node - linkType: hard - "history@npm:^5.0.0": version: 5.3.0 resolution: "history@npm:5.3.0" @@ -6831,13 +6824,6 @@ __metadata: languageName: node linkType: hard -"hoist-non-react-statics@npm:^2.5.0": - version: 2.5.5 - resolution: "hoist-non-react-statics@npm:2.5.5" - checksum: ee2d05e5c7e1398ad84a15b0327f66bd78f38a8e0015e852f954b36434e32eb7e942d5357505020a3a1147f247b165bf1e69d72393e3accab67cafdafeb86230 - languageName: node - linkType: hard - "hoist-non-react-statics@npm:^3.3.0": version: 3.3.2 resolution: "hoist-non-react-statics@npm:3.3.2" @@ -8398,7 +8384,7 @@ __metadata: languageName: node linkType: hard -"loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0, loose-envify@npm:^1.2.0, loose-envify@npm:^1.3.1, loose-envify@npm:^1.4.0": +"loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" dependencies: @@ -8937,8 +8923,8 @@ __metadata: react-markdown: ^5.0.3 react-picky: ^5.3.2 react-redux: ^5.0.5 - react-router: ^4.1.1 - react-router-dom: ^4.1.1 + react-router: ^7.0.0 + react-router-dom: ^7.0.0 react-select: ^3.0.4 react-test-renderer: ^16.8.4 react-zendesk: ^0.1.5 @@ -10489,36 +10475,31 @@ __metadata: languageName: node linkType: hard -"react-router-dom@npm:^4.1.1": - version: 4.3.1 - resolution: "react-router-dom@npm:4.3.1" +"react-router-dom@npm:^7.0.0": + version: 7.6.2 + resolution: "react-router-dom@npm:7.6.2" dependencies: - history: ^4.7.2 - invariant: ^2.2.4 - loose-envify: ^1.3.1 - prop-types: ^15.6.1 - react-router: ^4.3.1 - warning: ^4.0.1 + react-router: 7.6.2 peerDependencies: - react: ">=15" - checksum: e73b12fc97d1019a63c6ab5862a491634b8d9e5a44f954b0831913f5853faccab364eac3c1eb3563c74998efeb8675f857a4fa9aa1f6d3b46368557f5a6f935b + react: ">=18" + react-dom: ">=18" + checksum: 944f87d4d62eddbd517b9cbd39c9282200b172b31309cdb0eca3803f768c2b29466d852db7ce4a997ae1818d8103a1d1322e7fca4d6d4d846757b0e00b931c90 languageName: node linkType: hard -"react-router@npm:^4.1.1, react-router@npm:^4.3.1": - version: 4.3.1 - resolution: "react-router@npm:4.3.1" +"react-router@npm:7.6.2, react-router@npm:^7.0.0": + version: 7.6.2 + resolution: "react-router@npm:7.6.2" dependencies: - history: ^4.7.2 - hoist-non-react-statics: ^2.5.0 - invariant: ^2.2.4 - loose-envify: ^1.3.1 - path-to-regexp: ^1.7.0 - prop-types: ^15.6.1 - warning: ^4.0.1 + cookie: ^1.0.1 + set-cookie-parser: ^2.6.0 peerDependencies: - react: ">=15" - checksum: 144f2167e4589ec1eea3d9d178cf18571ee20bbd4abe8a46518dda91cd28db9da78c11b52cfd92d1bdb1e3d38b9751fa173ed85ced4e43a76f89cacd3502cf88 + react: ">=18" + react-dom: ">=18" + peerDependenciesMeta: + react-dom: + optional: true + checksum: 6d3c931043dcbc3dd16dc431e3a7701403c2a786068968154374d1bbcb7476b8ae7ae9279ed81cc9c36ce537b8d7355c13d126045cd52c98decc3bd1194cc4f9 languageName: node linkType: hard @@ -10966,13 +10947,6 @@ __metadata: languageName: node linkType: hard -"resolve-pathname@npm:^3.0.0": - version: 3.0.0 - resolution: "resolve-pathname@npm:3.0.0" - checksum: 6147241ba42c423dbe83cb067a2b4af4f60908c3af57e1ea567729cc71416c089737fe2a73e9e79e7a60f00f66c91e4b45ad0d37cd4be2d43fec44963ef14368 - languageName: node - linkType: hard - "resolve@npm:^1.1.6, resolve@npm:^1.12.0, resolve@npm:^1.14.2, resolve@npm:^1.9.0": version: 1.22.10 resolution: "resolve@npm:1.22.10" @@ -11384,6 +11358,13 @@ __metadata: languageName: node linkType: hard +"set-cookie-parser@npm:^2.6.0": + version: 2.7.1 + resolution: "set-cookie-parser@npm:2.7.1" + checksum: 2ef8b351094712f8f7df6d63ed4b10350b24a5b515772690e7dec227df85fcef5bc451c7765f484fd9f36694ece5438d1456407d017f237d0d3351d7dbbd3587 + languageName: node + linkType: hard + "set-function-length@npm:^1.2.2": version: 1.2.2 resolution: "set-function-length@npm:1.2.2" @@ -12364,14 +12345,7 @@ __metadata: languageName: node linkType: hard -"tiny-invariant@npm:^1.0.2": - version: 1.3.3 - resolution: "tiny-invariant@npm:1.3.3" - checksum: 5e185c8cc2266967984ce3b352a4e57cb89dad5a8abb0dea21468a6ecaa67cd5bb47a3b7a85d08041008644af4f667fb8b6575ba38ba5fb00b3b5068306e59fe - languageName: node - linkType: hard - -"tiny-warning@npm:^1.0.0, tiny-warning@npm:^1.0.2": +"tiny-warning@npm:^1.0.2": version: 1.0.3 resolution: "tiny-warning@npm:1.0.3" checksum: da62c4acac565902f0624b123eed6dd3509bc9a8d30c06e017104bedcf5d35810da8ff72864400ad19c5c7806fc0a8323c68baf3e326af7cb7d969f846100d71 @@ -12962,13 +12936,6 @@ __metadata: languageName: node linkType: hard -"value-equal@npm:^1.0.1": - version: 1.0.1 - resolution: "value-equal@npm:1.0.1" - checksum: bb7ae1facc76b5cf8071aeb6c13d284d023fdb370478d10a5d64508e0e6e53bb459c4bbe34258df29d82e6f561f874f0105eba38de0e61fe9edd0bdce07a77a2 - languageName: node - linkType: hard - "vary@npm:~1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" @@ -13014,7 +12981,7 @@ __metadata: languageName: node linkType: hard -"warning@npm:^4.0.1, warning@npm:^4.0.2, warning@npm:^4.0.3": +"warning@npm:^4.0.2, warning@npm:^4.0.3": version: 4.0.3 resolution: "warning@npm:4.0.3" dependencies: