diff --git a/res/css/_components.pcss b/res/css/_components.pcss index b7bfde21d8..17ab3c0bb2 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -92,6 +92,7 @@ @import "./structures/auth/_CompleteSecurity.pcss"; @import "./structures/auth/_ConfirmSessionLockTheftView.pcss"; @import "./structures/auth/_Login.pcss"; +@import "./structures/auth/_LoginSplashView.pcss"; @import "./structures/auth/_Registration.pcss"; @import "./structures/auth/_SessionLockStolenView.pcss"; @import "./structures/auth/_SetupEncryptionBody.pcss"; diff --git a/res/css/structures/_MatrixChat.pcss b/res/css/structures/_MatrixChat.pcss index 9aef8293e2..cdbe2fcfef 100644 --- a/res/css/structures/_MatrixChat.pcss +++ b/res/css/structures/_MatrixChat.pcss @@ -19,13 +19,6 @@ limitations under the License. height: 100%; } -.mx_MatrixChat_splashButtons { - text-align: center; - width: 100%; - position: absolute; - bottom: 30px; -} - .mx_MatrixChat_wrapper { display: flex; @@ -50,18 +43,6 @@ limitations under the License. min-height: 0; } -.mx_MatrixChat_syncError { - color: $accent-fg-color; - background-color: #df2a8b; /* Only used here */ - border-radius: 5px; - display: table; - padding: 30px; - position: absolute; - top: 100px; - left: 50%; - transform: translateX(-50%); -} - /* not the left panel, and not the resize handle, so the roomview and friends */ .mx_MatrixChat > :not(.mx_LeftPanel):not(.mx_SpacePanel):not(.mx_ResizeHandle):not(.mx_LeftPanel_outerWrapper) { background-color: $background; diff --git a/res/css/structures/auth/_LoginSplashView.pcss b/res/css/structures/auth/_LoginSplashView.pcss new file mode 100644 index 0000000000..6088383122 --- /dev/null +++ b/res/css/structures/auth/_LoginSplashView.pcss @@ -0,0 +1,34 @@ +/* +Copyright 2015-2024 The Matrix.org Foundation C.I.C. + +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. +*/ + +.mx_LoginSplashView_splashButtons { + text-align: center; + width: 100%; + position: absolute; + bottom: 30px; +} + +.mx_LoginSplashView_syncError { + color: $accent-fg-color; + background-color: #df2a8b; /* Only used here */ + border-radius: 5px; + display: table; + padding: 30px; + position: absolute; + top: 100px; + left: 50%; + transform: translateX(-50%); +} diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 0e7d8e866b..b7572089c9 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1,5 +1,5 @@ /* -Copyright 2015-2022 The Matrix.org Foundation C.I.C. +Copyright 2015-2024 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -60,7 +60,6 @@ import { _t, _td, getCurrentLanguage } from "../../languageHandler"; import SettingsStore from "../../settings/SettingsStore"; import ThemeController from "../../settings/controllers/ThemeController"; import { startAnyRegistrationFlow } from "../../Registration"; -import { messageForSyncError } from "../../utils/ErrorUtils"; import ResizeNotifier from "../../utils/ResizeNotifier"; import AutoDiscoveryUtils from "../../utils/AutoDiscoveryUtils"; import DMRoomMap from "../../utils/DMRoomMap"; @@ -113,7 +112,7 @@ import { PosthogAnalytics } from "../../PosthogAnalytics"; import { initSentry } from "../../sentry"; import LegacyCallHandler from "../../LegacyCallHandler"; import { showSpaceInvite } from "../../utils/space"; -import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton"; +import { ButtonEvent } from "../views/elements/AccessibleButton"; import { ActionPayload } from "../../dispatcher/payloads"; import { SummarizedNotificationState } from "../../stores/notifications/SummarizedNotificationState"; import Views from "../../Views"; @@ -147,6 +146,7 @@ import { Filter } from "../views/dialogs/spotlight/Filter"; import { checkSessionLockFree, getSessionLock } from "../../utils/SessionLock"; import { SessionLockStolenView } from "./auth/SessionLockStolenView"; import { ConfirmSessionLockTheftView } from "./auth/ConfirmSessionLockTheftView"; +import { LoginSplashView } from "./auth/LoginSplashView"; // legacy export export { default as Views } from "../../Views"; @@ -2119,22 +2119,12 @@ export default class MatrixChat extends React.PureComponent { ); } else { // we think we are logged in, but are still waiting for the /sync to complete - let errorBox; - if (this.state.syncError && !isStoreError) { - errorBox = ( -
{messageForSyncError(this.state.syncError)}
- ); - } + // Suppress `InvalidStoreError`s here, since they have their own error dialog. view = ( -
- {errorBox} - -
- - {_t("action|logout")} - -
-
+ ); } } else if (this.state.view === Views.WELCOME) { diff --git a/src/components/structures/auth/LoginSplashView.tsx b/src/components/structures/auth/LoginSplashView.tsx new file mode 100644 index 0000000000..d014ec18a2 --- /dev/null +++ b/src/components/structures/auth/LoginSplashView.tsx @@ -0,0 +1,58 @@ +/* +Copyright 2015-2024 The Matrix.org Foundation C.I.C. + +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 React from "react"; + +import { messageForSyncError } from "../../../utils/ErrorUtils"; +import Spinner from "../../views/elements/Spinner"; +import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton"; +import { _t } from "../../../languageHandler"; + +interface Props { + /** + * A callback function. Will be called if the user clicks the "logout" button on the splash screen. + * + * @param event - The click event + */ + onLogoutClick: (event: ButtonEvent) => void; + + /** + * Error that caused `/sync` to fail. If set, an error message will be shown on the splash screen. + */ + syncError: Error | null; +} + +/** + * The view that is displayed after we have logged in, before the first /sync is completed. + */ +export function LoginSplashView(props: Props): React.JSX.Element { + let errorBox: React.JSX.Element | undefined; + + if (props.syncError) { + errorBox =
{messageForSyncError(props.syncError)}
; + } + return ( +
+ {errorBox} + +
+ + {_t("action|logout")} + +
+
+ ); +} diff --git a/test/components/structures/auth/LoginSplashView-test.tsx b/test/components/structures/auth/LoginSplashView-test.tsx new file mode 100644 index 0000000000..84fabcfefb --- /dev/null +++ b/test/components/structures/auth/LoginSplashView-test.tsx @@ -0,0 +1,49 @@ +/* +Copyright 2024 The Matrix.org Foundation C.I.C. + +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 { render, RenderResult } from "@testing-library/react"; +import React, { ComponentProps } from "react"; + +import { LoginSplashView } from "../../../../src/components/structures/auth/LoginSplashView"; + +describe("", () => { + function getComponent(props: Partial> = {}): RenderResult { + const defaultProps = { + onLogoutClick: () => {}, + syncError: null, + }; + return render(); + } + + it("Renders a spinner", () => { + const rendered = getComponent(); + expect(rendered.getByTestId("spinner")).toBeInTheDocument(); + expect(rendered.asFragment()).toMatchSnapshot(); + }); + + it("Renders an error message", () => { + const rendered = getComponent({ syncError: new Error("boohoo") }); + expect(rendered.asFragment()).toMatchSnapshot(); + }); + + it("Calls onLogoutClick", () => { + const onLogoutClick = jest.fn(); + const rendered = getComponent({ onLogoutClick }); + expect(onLogoutClick).not.toHaveBeenCalled(); + rendered.getByRole("button", { name: "Logout" }).click(); + expect(onLogoutClick).toHaveBeenCalled(); + }); +}); diff --git a/test/components/structures/auth/__snapshots__/LoginSplashView-test.tsx.snap b/test/components/structures/auth/__snapshots__/LoginSplashView-test.tsx.snap new file mode 100644 index 0000000000..d26455652c --- /dev/null +++ b/test/components/structures/auth/__snapshots__/LoginSplashView-test.tsx.snap @@ -0,0 +1,70 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` Renders a spinner 1`] = ` + +
+
+
+
+
+ +
+
+ +`; + +exports[` Renders an error message 1`] = ` + +
+
+
+ Unable to connect to Homeserver. Retrying… +
+
+
+
+
+
+ +
+
+ +`;