diff --git a/README.md b/README.md index edc3653031..b9227823e3 100644 --- a/README.md +++ b/README.md @@ -460,7 +460,6 @@ flowchart LR subgraph L1 A(/)-->DA(/datasets) A-->HE(/help) - A-->LO(/login) A-->LOOUT(/logout) A-->PR(/projects) A-->SEA(/search) diff --git a/client/src/App.jsx b/client/src/App.jsx index 9edf0cdfd0..9cc7a49b3e 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -31,7 +31,7 @@ import { Route, Switch } from "react-router-dom"; import { CompatRoute } from "react-router-dom-v5-compat"; import { ToastContainer } from "react-toastify"; -import { LoginHelper, LoginRedirect } from "./authentication"; +import { LoginHelper } from "./authentication"; import { Loader } from "./components/Loader"; import { DatasetCoordinator } from "./dataset/Dataset.state"; import LazyShowDataset from "./dataset/LazyShowDataset"; @@ -103,11 +103,6 @@ function CentralContentContainer(props) { Reproducible Data Science | Open Research | Renku - - - - - {props.user.logged ? ( diff --git a/client/src/App.test.jsx b/client/src/App.test.jsx index 645f14c4ee..c4e01c0907 100644 --- a/client/src/App.test.jsx +++ b/client/src/App.test.jsx @@ -12,7 +12,11 @@ import { createCoreApiVersionedUrlConfig } from "./utils/helpers/url"; describe("rendering", () => { const model = new StateModel(globalSchema); - const params = { WELCOME_PAGE: "Some text", STATUSPAGE_ID: "5bce9beff4ca" }; + const params = { + WELCOME_PAGE: "Some text", + STATUSPAGE_ID: "5bce9beff4ca", + GATEWAY_URL: "https://renkulab.io/api", + }; const fakeLocation = { pathname: "" }; it("renders anonymous user without crashing", async () => { diff --git a/client/src/authentication/Authentication.test.js b/client/src/authentication/Authentication.test.js index b70062bfc0..768d4a1850 100644 --- a/client/src/authentication/Authentication.test.js +++ b/client/src/authentication/Authentication.test.js @@ -24,8 +24,7 @@ */ import { describe, expect, it, vi } from "vitest"; -import { LoginHelper } from "./Authentication.container"; -import { createLoginUrl } from "./LoginRedirect"; +import { LoginHelper, RenkuQueryParams } from "./Authentication.container"; // Mock relevant react objects const location = { pathname: "", state: "", previous: "", search: "" }; @@ -62,24 +61,19 @@ async function dispatchStorageEvent(key, newValue) { describe("LoginHelper functions", () => { const { queryParams } = LoginHelper; - it("createLoginUrl", async () => { - const extraParam = `${queryParams.login}=${queryParams.loginValue}`; - - expect(createLoginUrl(url)).toBe(`${url}?${extraParam}`); - - const urlWithParam = `${url}?test=1`; - expect(createLoginUrl(urlWithParam)).toBe(`${urlWithParam}&${extraParam}`); - }); - it("handleLoginParams", async () => { localStorage.clear(); LoginHelper.handleLoginParams(history); expect(localStorage.length).toBe(0); - const loginUrl = createLoginUrl(url); + const loginUrl = new URL(url); + loginUrl.searchParams.set( + RenkuQueryParams.login, + RenkuQueryParams.loginValue + ); const loginHistory = { ...history, - location: { ...location, search: loginUrl.replace(url, "") }, + location: { ...location, search: loginUrl.href.replace(url, "") }, }; const datePre = new Date().getTime(); diff --git a/client/src/authentication/LoginRedirect.test.jsx b/client/src/authentication/LoginRedirect.test.jsx deleted file mode 100644 index 367631482d..0000000000 --- a/client/src/authentication/LoginRedirect.test.jsx +++ /dev/null @@ -1,60 +0,0 @@ -/*! - * Copyright 2020 - Swiss Data Science Center (SDSC) - * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and - * Eidgenössische Technische Hochschule Zürich (ETHZ). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * renku-ui - * - * Authentication.test.js - * Tests for authentication. - */ - -import { createRoot } from "react-dom/client"; -import { act } from "react-dom/test-utils"; -import { MemoryRouter } from "react-router-dom"; - -import { describe, it, vi } from "vitest"; - -import LoginRedirect from "./LoginRedirect"; - -// Mock relevant react objects -const location = { pathname: "", state: "", previous: "", search: "" }; -delete window.location; -window.location = { reload: vi.fn(), replace: vi.fn() }; - -// Mock localStorage event generator - -describe("rendering", () => { - const params = { BASE_URL: "https://fake" }; - it("renders Login", async () => { - const props = { - params, - location, - }; - - const div = document.createElement("div"); - document.body.appendChild(div); - const root = createRoot(div); - await act(async () => { - root.render( - - - - ); - }); - }); -}); diff --git a/client/src/authentication/LoginRedirect.tsx b/client/src/authentication/LoginRedirect.tsx deleted file mode 100644 index 69c41c3085..0000000000 --- a/client/src/authentication/LoginRedirect.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/*! - * Copyright 2023 - Swiss Data Science Center (SDSC) - * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and - * Eidgenössische Technische Hochschule Zürich (ETHZ). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { useContext, useEffect } from "react"; -import { useLocation } from "react-router"; - -import AppContext from "../utils/context/appContext"; -import { RenkuQueryParams } from "./Authentication.container"; - -// ? Exported for testing -export function createLoginUrl(url: string) { - const redirectUrl = new URL(url); - if (!redirectUrl.search.includes(RenkuQueryParams.login)) - redirectUrl.searchParams.append( - RenkuQueryParams.login, - RenkuQueryParams.loginValue - ); - - return redirectUrl.toString(); -} - -export default function LoginRedirect() { - const location = useLocation<{ previous?: string }>(); - const { params } = useContext(AppContext); - - useEffect(() => { - if (!params) { - return; - } - - // build redirect url - let url = params.BASE_URL; - // always pass "previous" with the current `location.pathname` - if (location.state && location.state.previous) - url += location.state.previous; - const redirectParam = encodeURIComponent(createLoginUrl(url)); - const uiServerUrl = params.UISERVER_URL; - const authUrl = `${uiServerUrl}/auth/login?redirect_url=${redirectParam}`; - - // set new location - window.location.replace(authUrl); - }, [location.state, params]); - return
; -} diff --git a/client/src/authentication/index.js b/client/src/authentication/index.js index 3fdce84a0d..d8f7412451 100644 --- a/client/src/authentication/index.js +++ b/client/src/authentication/index.js @@ -16,7 +16,6 @@ * limitations under the License. */ -import LoginRedirect from "./LoginRedirect.tsx"; import { LoginHelper } from "./Authentication.container.js"; -export { LoginHelper, LoginRedirect }; +export { LoginHelper }; diff --git a/client/src/authentication/useLoginUrl.hook.ts b/client/src/authentication/useLoginUrl.hook.ts new file mode 100644 index 0000000000..008f3c0900 --- /dev/null +++ b/client/src/authentication/useLoginUrl.hook.ts @@ -0,0 +1,66 @@ +/*! + * Copyright 2024 - Swiss Data Science Center (SDSC) + * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and + * Eidgenössische Technische Hochschule Zürich (ETHZ). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useContext, useEffect, useRef } from "react"; +import { useLocation } from "react-router-dom-v5-compat"; + +import AppContext from "../utils/context/appContext"; +import type { AppParams } from "../utils/context/appParams.types"; +import { RenkuQueryParams } from "./Authentication.container"; + +interface UseLoginUrlArgs { + params?: AppParams; + redirectUrl?: URL; +} + +export function useLoginUrl(args?: UseLoginUrlArgs): URL { + const { params: params_, redirectUrl: redirectUrl_ } = args ?? {}; + + const { params: appContextParams } = useContext(AppContext); + const params = params_ ?? appContextParams; + const gatewayUrl = params?.GATEWAY_URL; + + const location = useLocation(); + const windowLocationRef = useRef(window.location.href); + + useEffect(() => { + windowLocationRef.current = window.location.href; + }, [location]); + + if (!gatewayUrl) { + throw new Error("Cannot create login URL"); + } + + const redirectUrl = + redirectUrl_ ?? windowLocationRef.current + ? new URL(windowLocationRef.current) + : null; + if (redirectUrl && !redirectUrl.search.includes(RenkuQueryParams.login)) { + redirectUrl.searchParams.append( + RenkuQueryParams.login, + RenkuQueryParams.loginValue + ); + } + + const loginUrl = new URL(`${gatewayUrl}/auth/login`); + if (redirectUrl) { + loginUrl.searchParams.set("redirect_url", redirectUrl.href); + } + + return loginUrl; +} diff --git a/client/src/components/loginAlert/LoginAlert.tsx b/client/src/components/loginAlert/LoginAlert.tsx index fac6057b50..6cfd8f165f 100644 --- a/client/src/components/loginAlert/LoginAlert.tsx +++ b/client/src/components/loginAlert/LoginAlert.tsx @@ -23,10 +23,10 @@ * LoginAlert component */ +import cx from "classnames"; import { Alert } from "reactstrap"; -import { Link, useLocation } from "react-router-dom"; -import { Url } from "../../utils/helpers/url"; +import { useLoginUrl } from "../../authentication/useLoginUrl.hook"; export interface LoginAlertProps { logged: boolean; @@ -45,18 +45,15 @@ const LoginAlert = ({ textPost = " to use this feature.", textPre, }: LoginAlertProps) => { - const location = useLocation(); + const loginUrl = useLoginUrl(); // No need to show anything when the user is logged. if (logged) return null; - const loginUrl = Url.get(Url.pages.login.link, { - pathname: location.pathname, - }); const link = ( - + {textLogin} - + ); const introElement = textIntro ?

{textIntro}

: null; diff --git a/client/src/components/navbar/LoggedInNavBar.tsx b/client/src/components/navbar/LoggedInNavBar.tsx index 25108965af..ea04bfa37f 100644 --- a/client/src/components/navbar/LoggedInNavBar.tsx +++ b/client/src/components/navbar/LoggedInNavBar.tsx @@ -119,8 +119,7 @@ export default function LoggedInNavBar({ /> - {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */} - + diff --git a/client/src/components/navbar/NavBarItems.tsx b/client/src/components/navbar/NavBarItems.tsx index 8cfc2c8990..5f43803ed4 100644 --- a/client/src/components/navbar/NavBarItems.tsx +++ b/client/src/components/navbar/NavBarItems.tsx @@ -28,10 +28,12 @@ import { DropdownItem, DropdownMenu, DropdownToggle, + NavLink, UncontrolledDropdown, } from "reactstrap"; import { LoginHelper } from "../../authentication"; +import { useLoginUrl } from "../../authentication/useLoginUrl.hook"; import AdminDropdownItem from "../../landing/AdminDropdownItem"; import { User } from "../../model/renkuModels.types"; import NotificationsMenu from "../../notifications/NotificationsMenu"; @@ -43,10 +45,8 @@ import { getActiveProjectPathWithNamespace, gitLabUrlFromProfileUrl, } from "../../utils/helpers/HelperFunctions"; -import { Url } from "../../utils/helpers/url"; import { ExternalDocsLink, ExternalLink } from "../ExternalLinks"; import { Loader } from "../Loader"; -import { RenkuNavLink } from "../RenkuNavLink"; import BootstrapGitLabIcon from "../icons/BootstrapGitLabIcon"; import styles from "./NavBarItem.module.scss"; @@ -284,8 +284,6 @@ interface RenkuToolbarItemUserProps { } export function RenkuToolbarItemUser({ params }: RenkuToolbarItemUserProps) { - const location = useLocation(); - const user = useLegacySelector((state) => state.stateModel.user); const { renku10Enabled } = useAppSelector(({ featureFlags }) => featureFlags); @@ -294,11 +292,16 @@ export function RenkuToolbarItemUser({ params }: RenkuToolbarItemUserProps) { const uiserverURL = params.UISERVER_URL; const redirect_url = encodeURIComponent(params.BASE_URL); + const loginUrl = useLoginUrl({ params }); + if (!user.fetched) { return ; } else if (!user.logged) { - const to = Url.get(Url.pages.login.link, { pathname: location.pathname }); - return ; + return ( + + Login + + ); } return ( diff --git a/client/src/components/router/MemoryRouter.tsx b/client/src/components/router/MemoryRouter.tsx new file mode 100644 index 0000000000..086ae0833c --- /dev/null +++ b/client/src/components/router/MemoryRouter.tsx @@ -0,0 +1,34 @@ +/*! + * Copyright 2024 - Swiss Data Science Center (SDSC) + * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and + * Eidgenössische Technische Hochschule Zürich (ETHZ). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ReactNode } from "react"; +import { MemoryRouter as BaseRouter } from "react-router-dom"; +import { CompatRouter } from "react-router-dom-v5-compat"; + +interface RouterProps { + children?: ReactNode; +} + +/** Temporary router while routing is being upgraded from react-router@v5 to v6. Used for tests. */ +export default function MemoryRouter({ children }: RouterProps) { + return ( + + {children} + + ); +} diff --git a/client/src/dataset/DatasetError.jsx b/client/src/dataset/DatasetError.jsx index 5cc7430474..353a259523 100644 --- a/client/src/dataset/DatasetError.jsx +++ b/client/src/dataset/DatasetError.jsx @@ -16,12 +16,10 @@ * limitations under the License. */ -import { Link, useLocation } from "react-router-dom"; - +import { useLoginUrl } from "../authentication/useLoginUrl.hook"; import { ErrorAlert } from "../components/Alert"; import LoginAlert from "../components/loginAlert/LoginAlert"; import NotFound from "../not-found/NotFound"; -import { Url } from "../utils/helpers/url"; /** * incubator-renku-ui @@ -33,10 +31,7 @@ import { Url } from "../utils/helpers/url"; function DatasetError({ fetchError, insideProject, logged }) { const textPre = "You might need to be logged in to see this dataset. "; const textPost = "and try again."; - const location = useLocation(); - const loginUrl = Url.get(Url.pages.login.link, { - pathname: location.pathname, - }); + const loginUrl = useLoginUrl(); // inside project case if (insideProject) { @@ -95,9 +90,9 @@ function DatasetError({ fetchError, insideProject, logged }) { ) : (

{textPre} - + Log in - + {textPost}

); diff --git a/client/src/dataset/Datasets.test.jsx b/client/src/dataset/Datasets.test.jsx index 70c8caa44e..82c2c82b8c 100644 --- a/client/src/dataset/Datasets.test.jsx +++ b/client/src/dataset/Datasets.test.jsx @@ -27,10 +27,10 @@ import { createMemoryHistory } from "history"; import { createRoot } from "react-dom/client"; import { act } from "react-dom/test-utils"; import { Provider } from "react-redux"; -import { MemoryRouter } from "react-router-dom"; import { describe, expect, it } from "vitest"; import { testClient as client } from "../api-client"; +import MemoryRouter from "../components/router/MemoryRouter"; import { StateModel, globalSchema } from "../model"; import ShowDataset from "./Dataset.container"; import { mapDataset } from "./DatasetFunctions"; diff --git a/client/src/features/ProjectPageV2/ProjectPageContent/CodeRepositories/CodeRepositoryDisplay.tsx b/client/src/features/ProjectPageV2/ProjectPageContent/CodeRepositories/CodeRepositoryDisplay.tsx index 1a2581064f..196a7bbfa2 100644 --- a/client/src/features/ProjectPageV2/ProjectPageContent/CodeRepositories/CodeRepositoryDisplay.tsx +++ b/client/src/features/ProjectPageV2/ProjectPageContent/CodeRepositories/CodeRepositoryDisplay.tsx @@ -15,16 +15,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { skipToken } from "@reduxjs/toolkit/query"; import cx from "classnames"; import { useCallback, useEffect, useMemo, useState } from "react"; import { BoxArrowUpRight, - Pencil, CircleFill, + Pencil, Trash, XLg, } from "react-bootstrap-icons"; import { Controller, useForm } from "react-hook-form"; +import { Link } from "react-router-dom-v5-compat"; import { Badge, Button, @@ -43,31 +45,29 @@ import { OffcanvasBody, Row, } from "reactstrap"; -import { skipToken } from "@reduxjs/toolkit/query"; -import { Loader } from "../../../../components/Loader"; -import { RtkOrNotebooksError } from "../../../../components/errors/RtkErrorAlert"; -import { safeNewUrl } from "../../../../utils/helpers/safeNewUrl.utils"; -import { Project } from "../../../projectsV2/api/projectV2.api"; -import { usePatchProjectsByProjectIdMutation } from "../../../projectsV2/api/projectV2.enhanced-api"; +import { useLoginUrl } from "../../../../authentication/useLoginUrl.hook"; import { ErrorAlert, RenkuAlert, WarnAlert, } from "../../../../components/Alert"; +import { Loader } from "../../../../components/Loader"; +import { ButtonWithMenuV2 } from "../../../../components/buttons/Button"; +import { RtkOrNotebooksError } from "../../../../components/errors/RtkErrorAlert"; +import { ABSOLUTE_ROUTES } from "../../../../routing/routes.constants"; import useLegacySelector from "../../../../utils/customHooks/useLegacySelector.hook"; +import { safeNewUrl } from "../../../../utils/helpers/safeNewUrl.utils"; import connectedServicesApi, { useGetProvidersQuery, } from "../../../connectedServices/connectedServices.api"; import { INTERNAL_GITLAB_PROVIDER_ID } from "../../../connectedServices/connectedServices.constants"; +import { Project } from "../../../projectsV2/api/projectV2.api"; +import { usePatchProjectsByProjectIdMutation } from "../../../projectsV2/api/projectV2.enhanced-api"; import repositoriesApi, { useGetRepositoryMetadataQuery, useGetRepositoryProbeQuery, } from "../../../repositories/repositories.api"; -import { Link, useLocation } from "react-router-dom-v5-compat"; -import { ABSOLUTE_ROUTES } from "../../../../routing/routes.constants"; -import { Url } from "../../../../utils/helpers/url"; -import { ButtonWithMenuV2 } from "../../../../components/buttons/Button"; interface EditCodeRepositoryModalProps { project: Project; @@ -681,8 +681,6 @@ function RepositoryView({ function RepositoryPermissionsAlert({ repositoryUrl, }: RepositoryPermissionsProps) { - const location = useLocation(); - const userLogged = useLegacySelector( (state) => state.stateModel.user.logged ); @@ -731,9 +729,7 @@ function RepositoryPermissionsAlert({ ? "connected" : "not-connected"; - const loginUrl = Url.get(Url.pages.login.link, { - pathname: location.pathname, - }); + const loginUrl = useLoginUrl(); if (error && isNotFound) { const color = permissions.pull ? "warning" : "danger"; @@ -771,8 +767,8 @@ function RepositoryPermissionsAlert({

{!userLogged ? (

- You need to log in to perform pushes on - git repositories. + You need to log in to perform pushes + on git repositories.

) : provider && status === "not-connected" ? (

@@ -798,8 +794,8 @@ function RepositoryPermissionsAlert({

{!userLogged ? (

- You need to log in to perform pushes on - git repositories. + You need to log in to perform pushes + on git repositories.

) : provider && status === "not-connected" ? (

diff --git a/client/src/features/session/components/SessionSaveWarning.tsx b/client/src/features/session/components/SessionSaveWarning.tsx index 1d2dce9a14..f59e829f09 100644 --- a/client/src/features/session/components/SessionSaveWarning.tsx +++ b/client/src/features/session/components/SessionSaveWarning.tsx @@ -18,11 +18,10 @@ import cx from "classnames"; import { useCallback, useContext, useState } from "react"; -import { useLocation } from "react-router"; -import { Link } from "react-router-dom"; import { Button, Modal } from "reactstrap"; import { ACCESS_LEVELS } from "../../../api-client"; +import { useLoginUrl } from "../../../authentication/useLoginUrl.hook"; import { InfoAlert } from "../../../components/Alert"; import { ExternalLink } from "../../../components/ExternalLinks"; import { User } from "../../../model/renkuModels.types"; @@ -31,11 +30,8 @@ import { ForkProject } from "../../../project/new"; import { Docs } from "../../../utils/constants/Docs"; import AppContext from "../../../utils/context/appContext"; import useLegacySelector from "../../../utils/customHooks/useLegacySelector.hook"; -import { Url } from "../../../utils/helpers/url"; export default function SessionSaveWarning() { - const location = useLocation(); - const logged = useLegacySelector( (state) => state.stateModel.user.logged ); @@ -43,11 +39,9 @@ export default function SessionSaveWarning() { (state) => state.stateModel.project.metadata ); - if (!logged) { - const loginUrl = Url.get(Url.pages.login.link, { - pathname: location.pathname, - }); + const loginUrl = useLoginUrl(); + if (!logged) { return (

@@ -62,9 +56,12 @@ export default function SessionSaveWarning() { , but you cannot save your work.

- + Log in - {" "} + {" "} for full access.

diff --git a/client/src/features/session/components/StartNewSession.tsx b/client/src/features/session/components/StartNewSession.tsx index d21bc219d5..993e7d68d8 100644 --- a/client/src/features/session/components/StartNewSession.tsx +++ b/client/src/features/session/components/StartNewSession.tsx @@ -26,10 +26,10 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import cx from "classnames"; import { useCallback, useContext, useEffect, useMemo, useState } from "react"; import { Redirect, useLocation } from "react-router"; -import { Link } from "react-router-dom"; import { Button, Col, DropdownItem, Form, Modal, Row } from "reactstrap"; import { ACCESS_LEVELS } from "../../../api-client"; +import { useLoginUrl } from "../../../authentication/useLoginUrl.hook"; import { InfoAlert, RenkuAlert, WarnAlert } from "../../../components/Alert"; import { ExternalLink } from "../../../components/ExternalLinks"; import { Loader } from "../../../components/Loader"; @@ -54,8 +54,8 @@ import useAppSelector from "../../../utils/customHooks/useAppSelector.hook"; import useLegacySelector from "../../../utils/customHooks/useLegacySelector.hook"; import { isFetchBaseQueryError } from "../../../utils/helpers/ApiErrors"; import { Url } from "../../../utils/helpers/url"; -import { getProvidedSensitiveFields } from "../../project/utils/projectCloudStorage.utils"; import { useCoreSupport } from "../../project/useProjectCoreSupport"; +import { getProvidedSensitiveFields } from "../../project/utils/projectCloudStorage.utils"; import { useStartSessionMutation } from "../sessions.api"; import startSessionSlice, { setError, @@ -542,8 +542,6 @@ function SessionCreateLink() { } function SessionSaveWarning() { - const location = useLocation(); - const logged = useLegacySelector( (state) => state.stateModel.user.logged ); @@ -551,11 +549,9 @@ function SessionSaveWarning() { (state) => state.stateModel.project.metadata ); - if (!logged) { - const loginUrl = Url.get(Url.pages.login.link, { - pathname: location.pathname, - }); + const loginUrl = useLoginUrl(); + if (!logged) { return (

@@ -570,9 +566,12 @@ function SessionSaveWarning() { , but you cannot save your work.

- + Log in - {" "} + {" "} for full access.

diff --git a/client/src/features/usersV2/show/UserRedirect.tsx b/client/src/features/usersV2/show/UserRedirect.tsx index 1b508d71f7..0377e7f0a9 100644 --- a/client/src/features/usersV2/show/UserRedirect.tsx +++ b/client/src/features/usersV2/show/UserRedirect.tsx @@ -20,19 +20,14 @@ import { skipToken } from "@reduxjs/toolkit/query"; import cx from "classnames"; import { useEffect } from "react"; import { ArrowLeft, BoxArrowInRight } from "react-bootstrap-icons"; -import { - Link, - generatePath, - useLocation, - useNavigate, -} from "react-router-dom-v5-compat"; +import { Link, generatePath, useNavigate } from "react-router-dom-v5-compat"; import { Col, Row } from "reactstrap"; +import { useLoginUrl } from "../../../authentication/useLoginUrl.hook"; import { Loader } from "../../../components/Loader"; import ContainerWrap from "../../../components/container/ContainerWrap"; import { ABSOLUTE_ROUTES } from "../../../routing/routes.constants"; import useLegacySelector from "../../../utils/customHooks/useLegacySelector.hook"; -import { Url } from "../../../utils/helpers/url"; import UserNotFound from "../../projectsV2/notFound/UserNotFound"; import { useGetUserQuery } from "../../user/dataServicesUser.api"; @@ -72,11 +67,7 @@ export default function UserRedirect() { } function NotLoggedIn() { - const location = useLocation(); - - const loginUrl = Url.get(Url.pages.login.link, { - pathname: location.pathname, - }); + const loginUrl = useLoginUrl(); return ( @@ -86,10 +77,13 @@ function NotLoggedIn() {

You can only view your own user page if you are logged in.

- + Log in - +

diff --git a/client/src/landing/HeroLanding/HeroLanding.tsx b/client/src/landing/HeroLanding/HeroLanding.tsx index b44f1311f2..18d0b8b03b 100644 --- a/client/src/landing/HeroLanding/HeroLanding.tsx +++ b/client/src/landing/HeroLanding/HeroLanding.tsx @@ -1,9 +1,11 @@ import cx from "classnames"; -import { Link } from "react-router-dom"; import { Button } from "reactstrap"; + +import { useLoginUrl } from "../../authentication/useLoginUrl.hook"; import { HomeHeader } from "../AnonymousHome"; import heroGraphic from "../Graphics/hero_graph.svg"; import { AnonymousHomeConfig } from "../anonymousHome.types"; + import styles from "./HeroLanding.module.scss"; interface HeroLandingProps extends AnonymousHomeConfig { @@ -12,6 +14,8 @@ interface HeroLandingProps extends AnonymousHomeConfig { export default function HeroLanding(props: HeroLandingProps) { const { scrollToGetStarted } = props; + const loginUrl = useLoginUrl(); + return (
@@ -50,14 +54,13 @@ export default function HeroLanding(props: HeroLandingProps) { > Try it out - Create an account - +
diff --git a/client/src/landing/anonymousHomeNav.tsx b/client/src/landing/anonymousHomeNav.tsx index fc89315bd5..61cc61fc17 100644 --- a/client/src/landing/anonymousHomeNav.tsx +++ b/client/src/landing/anonymousHomeNav.tsx @@ -21,6 +21,8 @@ import React from "react"; import { List } from "react-bootstrap-icons"; import { Link } from "react-router-dom"; import { Button, Col, Collapse, Nav, Navbar, NavItem, Row } from "reactstrap"; + +import { useLoginUrl } from "../authentication/useLoginUrl.hook"; import { ExternalLink } from "../components/ExternalLinks"; import { Docs, Links, RenkuPythonDocs } from "../utils/constants/Docs"; import { Url } from "../utils/helpers/url"; @@ -157,6 +159,9 @@ function TopNavLink({ title, to }: BottomNavLinkProps) { function TopNav() { const [isOpen, setIsOpen] = React.useState(false); const toggleOpen = () => setIsOpen(!isOpen); + + const loginUrl = useLoginUrl(); + return ( <>
@@ -173,14 +178,13 @@ function TopNav() {
- Login - +