diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md
index 063c13e532..f14785bdd7 100644
--- a/CHANGELOG.unreleased.md
+++ b/CHANGELOG.unreleased.md
@@ -15,6 +15,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
### Changed
- Updated the styling of the "welcome" screen for new users to be in line with the new branding. [#6904](https://github.com/scalableminds/webknossos/pull/6904)
+- Improved Terms-of-Service modal (e.g., allow to switch organization even when modal was blocking the remaining usage of WEBKNOSSOS). [#6930](https://github.com/scalableminds/webknossos/pull/6930)
### Fixed
- Fixed an issue with text hints not being visible on the logout page for dark mode users. [#6916](https://github.com/scalableminds/webknossos/pull/6916)
diff --git a/frontend/javascripts/components/terms_of_services_check.tsx b/frontend/javascripts/components/terms_of_services_check.tsx
index 2c9cbec3c6..63e85d9982 100644
--- a/frontend/javascripts/components/terms_of_services_check.tsx
+++ b/frontend/javascripts/components/terms_of_services_check.tsx
@@ -4,7 +4,7 @@ import {
getTermsOfService,
requiresTermsOfServiceAcceptance,
} from "admin/api/terms_of_service";
-import { Modal, Spin } from "antd";
+import { Dropdown, MenuProps, Modal, Space, Spin } from "antd";
import { AsyncButton } from "components/async_clickables";
import { useFetch } from "libs/react_helpers";
import UserLocalStorage from "libs/user_local_storage";
@@ -13,6 +13,11 @@ import type { OxalisState } from "oxalis/store";
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { formatDateInLocalTimeZone } from "./formatted_date";
+import { switchTo } from "navbar";
+import { getUsersOrganizations } from "admin/admin_rest_api";
+import { DownOutlined } from "@ant-design/icons";
+import _ from "lodash";
+import { APIUser } from "types/api_flow_types";
const SNOOZE_DURATION_IN_DAYS = 3;
const LAST_TERMS_OF_SERVICE_WARNING_KEY = "lastTermsOfServiceWarning";
@@ -68,6 +73,7 @@ export function CheckTermsOfServices() {
onAccept={onAccept}
isModalOpen={isModalOpen}
closeModal={closeModal}
+ activeUser={activeUser}
/>
);
} else {
@@ -76,21 +82,58 @@ export function CheckTermsOfServices() {
acceptanceInfo={acceptanceInfo}
isModalOpen={isModalOpen}
closeModal={closeModal}
+ activeUser={activeUser}
/>
);
}
}
+function OrganizationSwitchMenu({
+ activeUser,
+ style,
+}: {
+ activeUser: APIUser;
+ style?: React.CSSProperties;
+}) {
+ const { organization: organizationName } = activeUser;
+ const usersOrganizations = useFetch(getUsersOrganizations, [], []);
+ const switchableOrganizations = usersOrganizations.filter((org) => org.name !== organizationName);
+ const isMultiMember = switchableOrganizations.length > 0;
+
+ if (!isMultiMember) {
+ return null;
+ }
+
+ const items: MenuProps["items"] = switchableOrganizations.map((org) => ({
+ key: org.name,
+ onClick: () => switchTo(org),
+ label: org.displayName || org.name,
+ }));
+
+ return (
+
+ e.preventDefault()}>
+
+ Switch Organization
+
+
+
+
+ );
+}
+
function AcceptTermsOfServiceModal({
onAccept,
acceptanceInfo,
isModalOpen,
closeModal,
+ activeUser,
}: {
onAccept: (version: number) => Promise;
acceptanceInfo: AcceptanceInfo;
isModalOpen: boolean;
closeModal: () => void;
+ activeUser: APIUser;
}) {
const terms = useFetch(getTermsOfService, null, []);
@@ -101,10 +144,11 @@ function AcceptTermsOfServiceModal({
open={isModalOpen}
title="Terms of Services"
closable={!acceptanceInfo.acceptanceDeadlinePassed}
- onCancel={closeModal}
+ onCancel={acceptanceInfo.acceptanceDeadlinePassed ? _.noop : closeModal}
width={850}
maskClosable={false}
footer={[
+ ,
void;
+ activeUser: APIUser;
}) {
const deadlineExplanation = getDeadlineExplanation(acceptanceInfo);
return (
@@ -153,7 +199,7 @@ function TermsOfServiceAcceptanceMissingModal({
open={isModalOpen}
closable={!acceptanceInfo.acceptanceDeadlinePassed}
onCancel={closeModal}
- footer={null}
+ footer={[]}
maskClosable={false}
>
Please ask the organization owner to accept the terms of services. {deadlineExplanation}
diff --git a/frontend/javascripts/navbar.tsx b/frontend/javascripts/navbar.tsx
index e7e48033f8..0da97fa9bb 100644
--- a/frontend/javascripts/navbar.tsx
+++ b/frontend/javascripts/navbar.tsx
@@ -464,6 +464,21 @@ function NotificationIcon({ activeUser }: { activeUser: APIUser }) {
);
}
+export const switchTo = async (org: APIOrganization) => {
+ Toast.info(`Switching to ${org.displayName || org.name}`);
+
+ // If the user is currently at the datasets tab, the active folder is encoded
+ // in the URI. Switching to another organization means that the folder id
+ // becomes invalid. That's why, we are removing any identifiers from the
+ // current datasets path before reloading the page (which is done in
+ // switchToOrganization).
+ if (window.location.pathname.startsWith("/dashboard/datasets/")) {
+ window.history.replaceState({}, "", "/dashboard/datasets/");
+ }
+
+ await switchToOrganization(org.name);
+};
+
function LoggedInAvatar({
activeUser,
handleLogout,
@@ -477,21 +492,6 @@ function LoggedInAvatar({
? activeOrganization.displayName || activeOrganization.name
: organizationName;
- const switchTo = async (org: APIOrganization) => {
- Toast.info(`Switching to ${org.displayName || org.name}`);
-
- // If the user is currently at the datasets tab, the active folder is encoded
- // in the URI. Switching to another organization means that the folder id
- // becomes invalid. That's why, we are removing any identifiers from the
- // current datasets path before reloading the page (which is done in
- // switchToOrganization).
- if (window.location.pathname.startsWith("/dashboard/datasets/")) {
- window.history.replaceState({}, "", "/dashboard/datasets/");
- }
-
- await switchToOrganization(org.name);
- };
-
const setSelectedTheme = async (theme: APIUserTheme) => {
let newTheme = theme;