Skip to content

Update React Router to v6 #8739

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 36 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
0512cf9
WIP upgrade to react router v6
hotzenklotz Jul 1, 2025
8ce893a
WIP
hotzenklotz Jul 1, 2025
12ecf6d
WIP
hotzenklotz Jul 1, 2025
c5ceba3
WIP
hotzenklotz Jul 1, 2025
50c6462
WIP
hotzenklotz Jul 1, 2025
d824f38
WIP
hotzenklotz Jul 1, 2025
21b8da2
WIP
hotzenklotz Jul 2, 2025
8091ba7
WIP fix secureroute
hotzenklotz Jul 2, 2025
fa3f9eb
fix more secured routes
hotzenklotz Jul 2, 2025
090c8f3
formatting and typecheck
hotzenklotz Jul 2, 2025
c1936d4
refactor router as data router
hotzenklotz Jul 2, 2025
828c078
fix account and organization views
hotzenklotz Jul 2, 2025
5cf1959
Merge branch 'master' of github.com:scalableminds/webknossos into rea…
hotzenklotz Jul 2, 2025
f0ed0f3
fix task list prefill
hotzenklotz Jul 2, 2025
97b24e3
revert action_bar_view back to class component
hotzenklotz Jul 3, 2025
04a612c
move several components out of the router into the main
hotzenklotz Jul 3, 2025
eb0c104
Merge branch 'master' of github.com:scalableminds/webknossos into rea…
hotzenklotz Jul 4, 2025
0566060
fix features() not being loaded yet
hotzenklotz Jul 4, 2025
29655d2
fix type error
hotzenklotz Jul 4, 2025
a8c2877
apply CodeRabbit PR feedback
hotzenklotz Jul 4, 2025
e56305c
Update frontend/javascripts/router/route_wrappers.tsx
hotzenklotz Jul 4, 2025
d9858be
Update frontend/javascripts/router/route_wrappers.tsx
hotzenklotz Jul 4, 2025
998aa11
Update frontend/javascripts/router/route_wrappers.tsx
hotzenklotz Jul 4, 2025
52195d3
fix uselocation
hotzenklotz Jul 4, 2025
13b6a63
fix too many buttons in dashboard
MichaelBuessemeyer Jul 4, 2025
1223fc8
Merge branch 'react-router-v6' of github.com:scalableminds/webknossos…
MichaelBuessemeyer Jul 4, 2025
0ac4d5e
fix typo breaking the code
MichaelBuessemeyer Jul 4, 2025
5b95d96
Merge branch 'master' into react-router-v6
hotzenklotz Jul 4, 2025
1562fa5
formatting
hotzenklotz Jul 4, 2025
8085a83
Merge branch 'master' of github.com:scalableminds/webknossos into rea…
hotzenklotz Jul 7, 2025
3417036
apply PR feedback
hotzenklotz Jul 7, 2025
db7abe3
apply CodeRabbit feedback
hotzenklotz Jul 8, 2025
d842aad
added with_blocker HOC
hotzenklotz Jul 8, 2025
08f33c2
Merge remote-tracking branch 'origin' into react-router-v6
hotzenklotz Jul 8, 2025
4c512b5
blockers
hotzenklotz Jul 8, 2025
363032f
fix typing
hotzenklotz Jul 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions frontend/javascripts/admin/account/account_password_view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Alert, Button, Col, Form, Input, Row, Space } from "antd";
import Toast from "libs/toast";
import messages from "messages";
import { useState } from "react";
import { useHistory } from "react-router-dom";
import { useNavigate } from "react-router-dom";
import { logoutUserAction } from "viewer/model/actions/user_actions";
import Store from "viewer/store";
import { SettingsCard } from "./helpers/settings_card";
Expand All @@ -15,7 +15,7 @@ const { Password } = Input;
const MIN_PASSWORD_LENGTH = 8;

function AccountPasswordView() {
const history = useHistory();
const navigate = useNavigate();
const [form] = Form.useForm();
const [isResetPasswordVisible, setResetPasswordVisible] = useState(false);

Expand All @@ -25,7 +25,7 @@ function AccountPasswordView() {
Toast.success(messages["auth.reset_pw_confirmation"]);
await logoutUser();
Store.dispatch(logoutUserAction());
history.push("/auth/login");
navigate("/auth/login");
})
.catch((error) => {
console.error("Password change failed:", error);
Expand Down
16 changes: 4 additions & 12 deletions frontend/javascripts/admin/account/account_settings_view.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { SafetyOutlined, SettingOutlined, UserOutlined } from "@ant-design/icons";
import { Breadcrumb, Layout, Menu } from "antd";
import type { MenuItemGroupType } from "antd/es/menu/interface";
import { Redirect, Route, Switch, useHistory, useLocation } from "react-router-dom";
import AccountAuthTokenView from "./account_auth_token_view";
import AccountPasswordView from "./account_password_view";
import AccountProfileView from "./account_profile_view";
import { Outlet, useLocation, useNavigate } from "react-router-dom";

const { Sider, Content } = Layout;

Expand Down Expand Up @@ -46,7 +43,7 @@ const MENU_ITEMS: MenuItemGroupType[] = [

function AccountSettingsView() {
const location = useLocation();
const history = useHistory();
const navigate = useNavigate();
const selectedKey = location.pathname.split("/").filter(Boolean).pop() || "profile";

const breadcrumbItems = [
Expand All @@ -68,17 +65,12 @@ function AccountSettingsView() {
selectedKeys={[selectedKey]}
style={{ height: "100%", padding: 24 }}
items={MENU_ITEMS}
onClick={({ key }) => history.push(`/account/${key}`)}
onClick={({ key }) => navigate(`/account/${key}`)}
/>
</Sider>
<Content style={{ padding: "32px", minHeight: 280, maxWidth: 1000 }}>
<Breadcrumb style={{ marginBottom: "16px" }} items={breadcrumbItems} />
<Switch>
<Route path="/account/profile" component={AccountProfileView} />
<Route path="/account/password" component={AccountPasswordView} />
<Route path="/account/token" component={AccountAuthTokenView} />
<Route path="/account" render={() => <Redirect to="/account/profile" />} />
</Switch>
<Outlet />
</Content>
</Layout>
);
Expand Down
19 changes: 8 additions & 11 deletions frontend/javascripts/admin/auth/accept_invite_view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,19 @@ import { getOrganizationByInvite, joinOrganization, switchToOrganization } from
import { Button, Layout, Result, Spin } from "antd";
import { AsyncButton } from "components/async_clickables";
import { useFetch } from "libs/react_helpers";
import { useWkSelector } from "libs/react_hooks";
import Toast from "libs/toast";
import { location } from "libs/window";
import { useState } from "react";
import { useHistory } from "react-router-dom";
import type { APIUser } from "types/api_types";
import { useNavigate, useParams } from "react-router-dom";

const { Content } = Layout;

export default function AcceptInviteView({
token,
activeUser,
}: {
token: string;
activeUser: APIUser | null | undefined;
}) {
const history = useHistory();
export default function AcceptInviteView() {
const activeUser = useWkSelector((state) => state.activeUser);
const { token = "" } = useParams();
const navigate = useNavigate();

const [isAuthenticationModalOpen, setIsAuthenticationModalOpen] = useState(false);
const [targetOrganization, exception] = useFetch(
async () => {
Expand All @@ -46,7 +43,7 @@ export default function AcceptInviteView({
targetOrganization != null ? targetOrganization.name || targetOrganization.id : "unknown";

const onSuccessfulJoin = (userJustRegistered: boolean = false) => {
history.push("/dashboard");
navigate("/dashboard");

if (userJustRegistered) {
// Since the user just registered, the organization is already active.
Expand Down
20 changes: 11 additions & 9 deletions frontend/javascripts/admin/auth/finish_reset_password_view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,34 @@ import { LockOutlined } from "@ant-design/icons";
import { Button, Card, Col, Form, Input, Row } from "antd";
import Request from "libs/request";
import Toast from "libs/toast";
import { getUrlParamsObjectFromString } from "libs/utils";
import messages from "messages";
import { useHistory } from "react-router-dom";
import { useLocation, useNavigate } from "react-router-dom";

const FormItem = Form.Item;
const { Password } = Input;
type Props = {
resetToken: string;
};

function FinishResetPasswordView(props: Props) {
function FinishResetPasswordView() {
const location = useLocation();
const { token } = getUrlParamsObjectFromString(location.search);
Comment on lines +13 to +14
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd unify how to extract the token from the url. in accept_invite_view.tsx you use

const { token = "" } = useParams();


const [form] = Form.useForm();
const history = useHistory();
const navigate = useNavigate();

function onFinish(formValues: Record<string, any>) {
const data = formValues;

if (props.resetToken === "") {
if (token == null) {
Toast.error(messages["auth.reset_token_not_supplied"]);
return;
}

data.token = props.resetToken;
data.token = token;
Request.sendJSONReceiveJSON("/api/auth/resetPassword", {
data,
}).then(() => {
Toast.success(messages["auth.reset_pw_confirmation"]);
history.push("/auth/login");
navigate("/auth/login");
});
}

Expand Down
8 changes: 4 additions & 4 deletions frontend/javascripts/admin/auth/login_view.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { Card, Col, Row } from "antd";
import * as Utils from "libs/utils";
import window from "libs/window";
import { useHistory } from "react-router-dom";
import { useNavigate } from "react-router-dom";
import LoginForm from "./login_form";

type Props = {
redirect?: string;
};

function LoginView({ redirect }: Props) {
const history = useHistory();
const navigate = useNavigate();
const onLoggedIn = () => {
if (!Utils.hasUrlParam("redirectPage")) {
if (redirect) {
// Use "redirect" prop for internal redirects, e.g. for SecuredRoutes
history.push(redirect);
navigate(redirect);
} else {
history.push("/dashboard");
navigate("/dashboard");
}
} else {
// Use "redirectPage" URL parameter to cause a full page reload and redirecting to external sites
Expand Down
12 changes: 6 additions & 6 deletions frontend/javascripts/admin/auth/registration_view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import features from "features";
import Toast from "libs/toast";
import messages from "messages";
import { useEffect, useState } from "react";
import { Link, useHistory } from "react-router-dom";
import { Link, useNavigate } from "react-router-dom";
import type { APIOrganization } from "types/api_types";

function RegistrationViewGeneric() {
const history = useHistory();
const navigate = useNavigate();
const [organization, setOrganization] = useState<APIOrganization | null>(null);
const [isLoading, setIsLoading] = useState(true);

Expand Down Expand Up @@ -55,10 +55,10 @@ function RegistrationViewGeneric() {
targetOrganization={organization}
onRegistered={(isUserLoggedIn?: boolean) => {
if (isUserLoggedIn) {
history.goBack();
navigate(-1);
} else {
Toast.success(messages["auth.account_created"]);
history.push("/auth/login");
navigate("/auth/login");
}
}}
/>
Expand Down Expand Up @@ -95,15 +95,15 @@ function RegistrationViewGeneric() {
}

function RegistrationViewWkOrg() {
const history = useHistory();
const navigate = useNavigate();
return (
<Row justify="center" align="middle" className="login-view">
<Col>
<Card className="login-content drawing-signup" style={{ maxWidth: 1000 }}>
<h3>Sign Up</h3>
<RegistrationFormWKOrg
onRegistered={() => {
history.push("/dashboard");
navigate("/dashboard");
}}
/>
<p
Expand Down
6 changes: 3 additions & 3 deletions frontend/javascripts/admin/auth/start_reset_password_view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ import { Button, Card, Col, Form, Input, Row } from "antd";
import Request from "libs/request";
import Toast from "libs/toast";
import messages from "messages";
import { Link, useHistory } from "react-router-dom";
import { Link, useNavigate } from "react-router-dom";
const FormItem = Form.Item;

function StartResetPasswordView() {
const [form] = Form.useForm();
const history = useHistory();
const navigate = useNavigate();

const onFinish = (formValues: Record<string, any>) => {
Request.sendJSONReceiveJSON("/api/auth/startResetPassword", {
data: formValues,
}).then(() => {
Toast.success(messages["auth.reset_email_notification"]);
history.push("/");
navigate("/");
});
};

Expand Down
11 changes: 6 additions & 5 deletions frontend/javascripts/admin/auth/verify_email_view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useFetch } from "libs/react_helpers";
import type { ServerErrorMessage } from "libs/request";
import Toast from "libs/toast";
import { useEffect } from "react";
import { useHistory } from "react-router-dom";
import { useNavigate, useParams } from "react-router-dom";
import { Store } from "viewer/singletons";

export const VERIFICATION_ERROR_TOAST_KEY = "verificationError";
Expand Down Expand Up @@ -44,8 +44,9 @@ export function showVerificationReminderToast() {
);
}

export default function VerifyEmailView({ token }: { token: string }) {
const history = useHistory();
export default function VerifyEmailView() {
const { token = "" } = useParams();
const navigate = useNavigate();
const [result, exception] = useFetch(
async () => {
try {
Expand All @@ -62,7 +63,7 @@ export default function VerifyEmailView({ token }: { token: string }) {
Toast.close(VERIFICATION_ERROR_TOAST_KEY);
}, []);

// biome-ignore lint/correctness/useExhaustiveDependencies: history.push is not needed as a dependency.
// biome-ignore lint/correctness/useExhaustiveDependencies: navigate is not needed as a dependency.
useEffect(() => {
if (result) {
Toast.success("Successfully verified your email.");
Expand All @@ -80,7 +81,7 @@ export default function VerifyEmailView({ token }: { token: string }) {
}

if (result || exception) {
history.push("/");
navigate("/");
}
}, [result, exception]);
return (
Expand Down
30 changes: 11 additions & 19 deletions frontend/javascripts/admin/dataset/dataset_add_remote_view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,19 @@ import DatasetSettingsDataTab, {
import { FormItemWithInfo, Hideable } from "dashboard/dataset/helper_components";
import FolderSelection from "dashboard/folders/folder_selection";
import { formatScale } from "libs/format_utils";
import { useWkSelector } from "libs/react_hooks";
import { readFileAsText } from "libs/read_file";
import Toast from "libs/toast";
import { jsonStringify } from "libs/utils";
import * as Utils from "libs/utils";
import _ from "lodash";
import messages from "messages";
import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
import { useHistory } from "react-router-dom";
import type { APIDataStore, APIUser } from "types/api_types";
import { useNavigate } from "react-router-dom";
import type { APIDataStore } from "types/api_types";
import type { ArbitraryObject } from "types/globals";
import type { DataLayer, DatasourceConfiguration } from "types/schemas/datasource.types";
import { Unicode } from "viewer/constants";
import type { WebknossosState } from "viewer/store";
import { Hint } from "viewer/view/action-bar/download_modal_view";
import { dataPrivacyInfo } from "./dataset_upload_view";

Expand All @@ -48,7 +47,7 @@ const { Password } = Input;

type FileList = UploadFile<any>[];

type OwnProps = {
type Props = {
onAdded: (
uploadedDatasetId: string,
updatedDatasetName: string,
Expand All @@ -61,11 +60,6 @@ type OwnProps = {
// the exploration and import.
defaultDatasetUrl?: string | null | undefined;
};
type StateProps = {
activeUser: APIUser | null | undefined;
};
type Props = OwnProps & StateProps;

function ensureLargestSegmentIdsInPlace(datasource: DatasourceConfiguration) {
for (const layer of datasource.dataLayers) {
if (layer.category === "color" || layer.largestSegmentId != null) {
Expand Down Expand Up @@ -180,7 +174,10 @@ export function GoogleAuthFormItem({
}

function DatasetAddRemoteView(props: Props) {
const { activeUser, onAdded, datastores, defaultDatasetUrl } = props;
const { activeUser } = useWkSelector((state) => ({
activeUser: state.activeUser,
}));
const { onAdded, datastores, defaultDatasetUrl } = props;

const uploadableDatastores = datastores.filter((datastore) => datastore.allowsUpload);
const hasOnlyOneDatastoreOrNone = uploadableDatastores.length <= 1;
Expand All @@ -192,7 +189,7 @@ function DatasetAddRemoteView(props: Props) {
const [targetFolderId, setTargetFolderId] = useState<string | null>(null);
const isDatasourceConfigStrFalsy = Form.useWatch("dataSourceJson", form) == null;
const maybeDataLayers = Form.useWatch(["dataSource", "dataLayers"], form);
const history = useHistory();
const navigate = useNavigate();

useEffect(() => {
const params = new URLSearchParams(location.search);
Expand All @@ -213,7 +210,7 @@ function DatasetAddRemoteView(props: Props) {
.getFieldError("datasetName")
.filter((error) => error === messages["dataset.name.already_taken"]);
if (maybeDSNameError == null) return;
history.push(
navigate(
`/datasets/${activeUser?.organization}/${form.getFieldValue(["dataSource", "id", "name"])}`,
);
};
Expand Down Expand Up @@ -729,9 +726,4 @@ function AddRemoteLayer({
);
}

const mapStateToProps = (state: WebknossosState): StateProps => ({
activeUser: state.activeUser,
});

const connector = connect(mapStateToProps);
export default connector(DatasetAddRemoteView);
export default DatasetAddRemoteView;
Loading
Loading