Make error screens more visually consistent (#2951)

This commit is contained in:
Robin
2025-01-17 04:35:39 -05:00
committed by GitHub
parent c218dc2f36
commit cda802a2e9
31 changed files with 334 additions and 175 deletions

View File

@@ -15,6 +15,7 @@ import {
import { type MatrixClient } from "matrix-js-sdk/src/client";
import { Trans, useTranslation } from "react-i18next";
import { Button, Heading, Text } from "@vector-im/compound-web";
import { OfflineIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { useNavigate } from "react-router-dom";
import { logger } from "matrix-js-sdk/src/logger";
@@ -25,9 +26,9 @@ import { Header, HeaderLogo, LeftNav, RightNav } from "../Header";
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
import { FieldRow, InputField } from "../input/Input";
import { StarRatingInput } from "../input/StarRatingInput";
import { RageshakeButton } from "../settings/RageshakeButton";
import { Link } from "../button/Link";
import { LinkButton } from "../button";
import { ErrorView } from "../ErrorView";
interface Props {
client: MatrixClient;
@@ -147,25 +148,17 @@ export const CallEndedView: FC<Props> = ({
return (
<>
<main className={styles.main}>
<Heading size="xl" weight="semibold" className={styles.headline}>
<Trans i18nKey="call_ended_view.body">
You were disconnected from the call
</Trans>
</Heading>
<div className={styles.disconnectedButtons}>
<ErrorView
Icon={OfflineIcon}
title={t("error.connection_lost")}
rageshake
>
<p>{t("error.connection_lost_description")}</p>
<Button onClick={reconnect}>
{t("call_ended_view.reconnect_button")}
</Button>
<div className={styles.rageshakeButton}>
<RageshakeButton description="***Call disconnected***" />
</div>
</div>
</ErrorView>
</main>
{!confineToRoom && (
<Text className={styles.footer}>
<Link to="/"> {t("return_home_button")} </Link>
</Text>
)}
</>
);
} else {

View File

@@ -21,7 +21,7 @@ import {
import { logger } from "matrix-js-sdk/src/logger";
import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
import { JoinRule } from "matrix-js-sdk/src/matrix";
import { Heading, Text } from "@vector-im/compound-web";
import { WebBrowserIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
@@ -54,11 +54,11 @@ import { useJoinRule } from "./useJoinRule";
import { InviteModal } from "./InviteModal";
import { useUrlParams } from "../UrlParams";
import { E2eeType } from "../e2ee/e2eeType";
import { Link } from "../button/Link";
import { useAudioContext } from "../useAudioContext";
import { callEventAudioSounds } from "./CallEventAudioRenderer";
import { useLatest } from "../useLatest";
import { usePageTitle } from "../usePageTitle";
import { ErrorView } from "../ErrorView";
declare global {
interface Window {
@@ -331,9 +331,9 @@ export const GroupCallView: FC<Props> = ({
// If we have a encryption system but the browser does not support it.
return (
<FullScreenView>
<Heading>{t("browser_media_e2ee_unsupported_heading")}</Heading>
<Text>{t("browser_media_e2ee_unsupported")}</Text>
<Link to="/">{t("common.home")}</Link>
<ErrorView Icon={WebBrowserIcon} title={t("error.e2ee_unsupported")}>
<p>{t("error.e2ee_unsupported_description")}</p>
</ErrorView>
</FullScreenView>
);
}

View File

@@ -14,13 +14,15 @@ import {
type JSX,
} from "react";
import { logger } from "matrix-js-sdk/src/logger";
import { useTranslation } from "react-i18next";
import { CheckIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { Trans, useTranslation } from "react-i18next";
import {
CheckIcon,
UnknownSolidIcon,
} from "@vector-im/compound-design-tokens/assets/web/icons";
import { type MatrixError } from "matrix-js-sdk/src/http-api";
import { Heading, Text } from "@vector-im/compound-web";
import { useClientLegacy } from "../ClientContext";
import { ErrorView, FullScreenView, LoadingView } from "../FullScreenView";
import { ErrorPage, FullScreenView, LoadingPage } from "../FullScreenView";
import { RoomAuthView } from "./RoomAuthView";
import { GroupCallView } from "./GroupCallView";
import { useRoomIdentifier, useUrlParams } from "../UrlParams";
@@ -37,6 +39,7 @@ import { useMuteStates } from "./MuteStates";
import { useOptInAnalytics } from "../settings/settings";
import { Config } from "../config/Config";
import { Link } from "../button/Link";
import { ErrorView } from "../ErrorView";
export const RoomPage: FC = () => {
const {
@@ -171,29 +174,40 @@ export const RoomPage: FC = () => {
if ((groupCallState.error as MatrixError).errcode === "M_NOT_FOUND") {
return (
<FullScreenView>
<Heading>{t("group_call_loader.failed_heading")}</Heading>
<Text>{t("group_call_loader.failed_text")}</Text>
{/* XXX: A 'create it for me' button would be the obvious UX here. Two screens already have
dupes of this flow, let's make a common component and put it here. */}
<Link to="/">{t("common.home")}</Link>
<ErrorView
Icon={UnknownSolidIcon}
title={t("error.call_not_found")}
>
<Trans i18nKey="error.call_not_found_description">
<p>
That link doesn't appear to belong to any existing call.
Check that you have the right link, or{" "}
<Link to="/">create a new one</Link>.
</p>
</Trans>
</ErrorView>
</FullScreenView>
);
} else if (groupCallState.error instanceof CallTerminatedMessage) {
return (
<FullScreenView>
<Heading>{groupCallState.error.message}</Heading>
<Text>{groupCallState.error.messageBody}</Text>
{groupCallState.error.reason && (
<>
{t("group_call_loader.reason")}:
<Text size="sm">"{groupCallState.error.reason}"</Text>
</>
)}
<Link to="/">{t("common.home")}</Link>
<ErrorView
Icon={groupCallState.error.icon}
title={groupCallState.error.message}
>
<p>{groupCallState.error.messageBody}</p>
{groupCallState.error.reason && (
<p>
{t("group_call_loader.reason", {
reason: groupCallState.error.reason,
})}
</p>
)}
</ErrorView>
</FullScreenView>
);
} else {
return <ErrorView error={groupCallState.error} />;
return <ErrorPage error={groupCallState.error} />;
}
default:
return <> </>;
@@ -202,9 +216,9 @@ export const RoomPage: FC = () => {
let content: ReactNode;
if (loading || isRegistering) {
content = <LoadingView />;
content = <LoadingPage />;
} else if (error) {
content = <ErrorView error={error} />;
content = <ErrorPage error={error} />;
} else if (!client) {
content = <RoomAuthView />;
} else if (!roomIdOrAlias) {

View File

@@ -5,7 +5,14 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { useState, useEffect, useRef, useCallback } from "react";
import {
useState,
useEffect,
useRef,
useCallback,
type ComponentType,
type SVGAttributes,
} from "react";
import { logger } from "matrix-js-sdk/src/logger";
import { EventType } from "matrix-js-sdk/src/@types/event";
import {
@@ -19,6 +26,11 @@ import { RoomEvent, type Room } from "matrix-js-sdk/src/models/room";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { JoinRule, MatrixError } from "matrix-js-sdk/src/matrix";
import { useTranslation } from "react-i18next";
import {
AdminIcon,
CloseIcon,
EndCallIcon,
} from "@vector-im/compound-design-tokens/assets/web/icons";
import { widget } from "../widget";
@@ -92,27 +104,25 @@ async function joinRoomAfterInvite(
export class CallTerminatedMessage extends Error {
/**
* @param messageBody The message explaining the kind of termination (kick, ban, knock reject, etc.) (translated)
*/
public messageBody: string;
/**
* @param reason The user provided reason for the termination (kick/ban)
*/
public reason?: string;
/**
*
* @param messageTitle The title of the call ended screen message (translated)
* @param messageBody The message explaining the kind of termination (kick, ban, knock reject, etc.) (translated)
* @param reason The user provided reason for the termination (kick/ban)
*/
public constructor(
/**
* The icon to display with the message.
*/
public readonly icon: ComponentType<SVGAttributes<SVGElement>>,
messageTitle: string,
messageBody: string,
reason?: string,
/**
* The message explaining the kind of termination (kick, ban, knock reject,
* etc.) (translated)
*/
public readonly messageBody: string,
/**
* The user-provided reason for the termination (kick/ban)
*/
public readonly reason?: string,
) {
super(messageTitle);
this.messageBody = messageBody;
this.reason = reason;
}
}
@@ -128,6 +138,7 @@ export const useLoadGroupCall = (
const bannedError = useCallback(
(): CallTerminatedMessage =>
new CallTerminatedMessage(
AdminIcon,
t("group_call_loader.banned_heading"),
t("group_call_loader.banned_body"),
leaveReason(),
@@ -137,6 +148,7 @@ export const useLoadGroupCall = (
const knockRejectError = useCallback(
(): CallTerminatedMessage =>
new CallTerminatedMessage(
CloseIcon,
t("group_call_loader.knock_reject_heading"),
t("group_call_loader.knock_reject_body"),
leaveReason(),
@@ -146,6 +158,7 @@ export const useLoadGroupCall = (
const removeNoticeError = useCallback(
(): CallTerminatedMessage =>
new CallTerminatedMessage(
EndCallIcon,
t("group_call_loader.call_ended_heading"),
t("group_call_loader.call_ended_body"),
leaveReason(),