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;