mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-13 17:05:43 +01:00
Read all errors with screen reader (#1839)
Make error notifications accessible for screen readers.
This commit is contained in:
2
gradle/changelog/screenreader_errors.yaml
Normal file
2
gradle/changelog/screenreader_errors.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
- type: added
|
||||
description: Read all errors with screen readers ([#1839](https://github.com/scm-manager/scm-manager/pull/1839))
|
||||
@@ -21,37 +21,19 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
import React from "react";
|
||||
import React, { FC } from "react";
|
||||
import { BackendError } from "@scm-manager/ui-api";
|
||||
import Notification from "./Notification";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
|
||||
type Props = WithTranslation & {
|
||||
type Props = {
|
||||
error: BackendError;
|
||||
};
|
||||
|
||||
class BackendErrorNotification extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
}
|
||||
const BackendErrorNotification: FC<Props> = ({ error }) => {
|
||||
const [t] = useTranslation("plugins");
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Notification type="danger">
|
||||
<div className="content">
|
||||
<p className="subtitle">{this.renderErrorName()}</p>
|
||||
<p>{this.renderErrorDescription()}</p>
|
||||
{this.renderAdditionalMessages()}
|
||||
<p>{this.renderViolations()}</p>
|
||||
{this.renderMetadata()}
|
||||
</div>
|
||||
</Notification>
|
||||
);
|
||||
}
|
||||
|
||||
renderErrorName = () => {
|
||||
const { error, t } = this.props;
|
||||
const renderErrorName = () => {
|
||||
const translation = t(`errors.${error.errorCode}.displayName`);
|
||||
if (translation === error.errorCode) {
|
||||
return error.message;
|
||||
@@ -59,8 +41,7 @@ class BackendErrorNotification extends React.Component<Props> {
|
||||
return translation;
|
||||
};
|
||||
|
||||
renderErrorDescription = () => {
|
||||
const { error, t } = this.props;
|
||||
const renderErrorDescription = () => {
|
||||
const translation = t(`errors.${error.errorCode}.description`);
|
||||
if (translation === error.errorCode) {
|
||||
return "";
|
||||
@@ -68,17 +49,16 @@ class BackendErrorNotification extends React.Component<Props> {
|
||||
return translation;
|
||||
};
|
||||
|
||||
renderAdditionalMessages = () => {
|
||||
const { error, t } = this.props;
|
||||
const renderAdditionalMessages = () => {
|
||||
if (error.additionalMessages) {
|
||||
return (
|
||||
<>
|
||||
<hr />
|
||||
{error.additionalMessages
|
||||
.map(additionalMessage =>
|
||||
.map((additionalMessage) =>
|
||||
additionalMessage.key ? t(`errors.${additionalMessage.key}.description`) : additionalMessage.message
|
||||
)
|
||||
.map(message => (
|
||||
.map((message) => (
|
||||
<p>{message}</p>
|
||||
))}
|
||||
<hr />
|
||||
@@ -87,8 +67,7 @@ class BackendErrorNotification extends React.Component<Props> {
|
||||
}
|
||||
};
|
||||
|
||||
renderViolations = () => {
|
||||
const { error, t } = this.props;
|
||||
const renderViolations = () => {
|
||||
if (error.violations) {
|
||||
return (
|
||||
<>
|
||||
@@ -110,17 +89,16 @@ class BackendErrorNotification extends React.Component<Props> {
|
||||
}
|
||||
};
|
||||
|
||||
renderMetadata = () => {
|
||||
const { error, t } = this.props;
|
||||
const renderMetadata = () => {
|
||||
return (
|
||||
<>
|
||||
{this.renderContext()}
|
||||
{this.renderMoreInformationLink()}
|
||||
{renderContext()}
|
||||
{renderMoreInformationLink()}
|
||||
<div className="level is-size-7">
|
||||
<div className="left">
|
||||
<div className="left" aria-hidden={true}>
|
||||
{t("errors.transactionId")} {error.transactionId}
|
||||
</div>
|
||||
<div className="right">
|
||||
<div className="right" aria-hidden={true}>
|
||||
{t("errors.errorCode")} {error.errorCode}
|
||||
</div>
|
||||
</div>
|
||||
@@ -128,8 +106,7 @@ class BackendErrorNotification extends React.Component<Props> {
|
||||
);
|
||||
};
|
||||
|
||||
renderContext = () => {
|
||||
const { error, t } = this.props;
|
||||
const renderContext = () => {
|
||||
if (error.context) {
|
||||
return (
|
||||
<>
|
||||
@@ -150,19 +127,34 @@ class BackendErrorNotification extends React.Component<Props> {
|
||||
}
|
||||
};
|
||||
|
||||
renderMoreInformationLink = () => {
|
||||
const { error, t } = this.props;
|
||||
const renderMoreInformationLink = () => {
|
||||
if (error.url) {
|
||||
return (
|
||||
<p>
|
||||
{t("errors.moreInfo")}{" "}
|
||||
<a href={error.url} target="_blank">
|
||||
<a href={error.url} target="_blank" rel="noreferrer" aria-label={t("error.link")}>
|
||||
{error.errorCode}
|
||||
</a>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default withTranslation("plugins")(BackendErrorNotification);
|
||||
return (
|
||||
<Notification type="danger" role="alert">
|
||||
<div className="content">
|
||||
<p className="subtitle">
|
||||
{t("error.subtitle")}
|
||||
{": "}
|
||||
{renderErrorName()}
|
||||
</p>
|
||||
<p>{renderErrorDescription()}</p>
|
||||
{renderAdditionalMessages()}
|
||||
<p>{renderViolations()}</p>
|
||||
{renderMetadata()}
|
||||
</div>
|
||||
</Notification>
|
||||
);
|
||||
};
|
||||
|
||||
export default BackendErrorNotification;
|
||||
|
||||
@@ -40,6 +40,16 @@ const LoginLink: FC = () => {
|
||||
return <a href={urls.withContextPath(`/login?from=${from}`)}>{t("errorNotification.loginLink")}</a>;
|
||||
};
|
||||
|
||||
const BasicErrorMessage: FC = ({ children }) => {
|
||||
const [t] = useTranslation("commons");
|
||||
|
||||
return (
|
||||
<Notification type="danger" role="alert">
|
||||
<strong>{t("errorNotification.prefix")}:</strong> {children}
|
||||
</Notification>
|
||||
);
|
||||
};
|
||||
|
||||
const ErrorNotification: FC<Props> = ({ error }) => {
|
||||
const [t] = useTranslation("commons");
|
||||
if (error) {
|
||||
@@ -47,22 +57,14 @@ const ErrorNotification: FC<Props> = ({ error }) => {
|
||||
return <BackendErrorNotification error={error} />;
|
||||
} else if (error instanceof UnauthorizedError) {
|
||||
return (
|
||||
<Notification type="danger">
|
||||
<strong>{t("errorNotification.prefix")}:</strong> {t("errorNotification.timeout")} <LoginLink />
|
||||
</Notification>
|
||||
<BasicErrorMessage>
|
||||
{t("errorNotification.timeout")} <LoginLink />
|
||||
</BasicErrorMessage>
|
||||
);
|
||||
} else if (error instanceof ForbiddenError) {
|
||||
return (
|
||||
<Notification type="danger">
|
||||
<strong>{t("errorNotification.prefix")}:</strong> {t("errorNotification.forbidden")}
|
||||
</Notification>
|
||||
);
|
||||
return <BasicErrorMessage>{t("errorNotification.forbidden")}</BasicErrorMessage>;
|
||||
} else {
|
||||
return (
|
||||
<Notification type="danger">
|
||||
<strong>{t("errorNotification.prefix")}:</strong> {error.message}
|
||||
</Notification>
|
||||
);
|
||||
return <BasicErrorMessage>{error.message}</BasicErrorMessage>;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
import React, { ReactNode } from "react";
|
||||
import React, { FC, ReactNode } from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
type NotificationType = "primary" | "info" | "success" | "warning" | "danger" | "inherit";
|
||||
@@ -31,33 +31,25 @@ type Props = {
|
||||
onClose?: () => void;
|
||||
className?: string;
|
||||
children?: ReactNode;
|
||||
role?: string;
|
||||
};
|
||||
|
||||
class Notification extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
type: "info"
|
||||
};
|
||||
|
||||
renderCloseButton() {
|
||||
const { onClose } = this.props;
|
||||
const Notification: FC<Props> = ({ type = "info", onClose, className, children, role }) => {
|
||||
const renderCloseButton = () => {
|
||||
if (onClose) {
|
||||
return <button className="delete" onClick={onClose} />;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
render() {
|
||||
const { type, className, children } = this.props;
|
||||
return null;
|
||||
};
|
||||
|
||||
const color = type !== "inherit" ? "is-" + type : "";
|
||||
|
||||
return (
|
||||
<div className={classNames("notification", color, className)}>
|
||||
{this.renderCloseButton()}
|
||||
<div className={classNames("notification", color, className)} role={role}>
|
||||
{renderCloseButton()}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default Notification;
|
||||
|
||||
@@ -73471,7 +73471,6 @@ exports[`Storyshots Notification Danger 1`] = `
|
||||
<div
|
||||
className="notification is-danger"
|
||||
>
|
||||
|
||||
Cleverness nuclear genuine static irresponsibility invited President Zaphod
|
||||
Beeblebrox hyperspace ship. Another custard through computer-generated universe
|
||||
shapes field strong disaster parties Russell’s ancestors infinite colour
|
||||
@@ -73487,7 +73486,6 @@ exports[`Storyshots Notification Info 1`] = `
|
||||
<div
|
||||
className="notification is-info"
|
||||
>
|
||||
|
||||
Cleverness nuclear genuine static irresponsibility invited President Zaphod
|
||||
Beeblebrox hyperspace ship. Another custard through computer-generated universe
|
||||
shapes field strong disaster parties Russell’s ancestors infinite colour
|
||||
@@ -73503,7 +73501,6 @@ exports[`Storyshots Notification Primary 1`] = `
|
||||
<div
|
||||
className="notification is-primary"
|
||||
>
|
||||
|
||||
Cleverness nuclear genuine static irresponsibility invited President Zaphod
|
||||
Beeblebrox hyperspace ship. Another custard through computer-generated universe
|
||||
shapes field strong disaster parties Russell’s ancestors infinite colour
|
||||
@@ -73519,7 +73516,6 @@ exports[`Storyshots Notification Success 1`] = `
|
||||
<div
|
||||
className="notification is-success"
|
||||
>
|
||||
|
||||
Cleverness nuclear genuine static irresponsibility invited President Zaphod
|
||||
Beeblebrox hyperspace ship. Another custard through computer-generated universe
|
||||
shapes field strong disaster parties Russell’s ancestors infinite colour
|
||||
@@ -73535,7 +73531,6 @@ exports[`Storyshots Notification Warning 1`] = `
|
||||
<div
|
||||
className="notification is-warning"
|
||||
>
|
||||
|
||||
Cleverness nuclear genuine static irresponsibility invited President Zaphod
|
||||
Beeblebrox hyperspace ship. Another custard through computer-generated universe
|
||||
shapes field strong disaster parties Russell’s ancestors infinite colour
|
||||
@@ -89309,7 +89304,6 @@ exports[`Storyshots Table|Table Empty 1`] = `
|
||||
<div
|
||||
className="notification is-info"
|
||||
>
|
||||
|
||||
No data found.
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -2294,7 +2294,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-one-third">
|
||||
<div class="notification is-danger">
|
||||
<div class="notification is-danger" role="alert">
|
||||
<button class="delete"> </button>
|
||||
<strong>Danger</strong><br>
|
||||
Primar lorem ipsum dolor sit amet, consectetur
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"error": {
|
||||
"subtitle": "Fehler",
|
||||
"link": "Link zu weiteren Informationen"
|
||||
},
|
||||
"changeset": {
|
||||
"contributor": {
|
||||
"type": {
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"error": {
|
||||
"subtitle": "Error",
|
||||
"link": "Link for more information"
|
||||
},
|
||||
"changeset": {
|
||||
"contributor": {
|
||||
"type": {
|
||||
@@ -471,4 +475,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
{{$content}}
|
||||
<h2 class="subtitle">An error occurred during SCM-Manager startup.</h2>
|
||||
|
||||
<div class="notification is-danger">
|
||||
<div class="notification is-danger" role="alert">
|
||||
<pre>
|
||||
{{ error }}
|
||||
</pre>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
{{$content}}
|
||||
<h2 class="subtitle">An error occurred during SCM-Manager startup.</h2>
|
||||
|
||||
<p class="notification is-danger">
|
||||
<p class="notification is-danger" role="alert">
|
||||
We cannot migrate your SCM-Manager 1 installation,
|
||||
because the version is too old.<br />
|
||||
Please migrate to version 1.60 or newer, before migration to 2.x.
|
||||
|
||||
Reference in New Issue
Block a user