/* * Copyright (c) 2020 - present Cloudogu GmbH * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License as published by the Free * Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see https://www.gnu.org/licenses/. */ import React, { FC, ReactNode, useEffect } from "react"; import { RouteComponentProps, useLocation, withRouter } from "react-router-dom"; import { useTranslation } from "react-i18next"; import classNames from "classnames"; import styled from "styled-components"; import { MissingLinkError, urls, useIndexLink } from "@scm-manager/ui-api"; import { useDocumentTitle } from "@scm-manager/ui-core"; import ErrorNotification from "./ErrorNotification"; import ErrorPage from "./ErrorPage"; import { Subtitle, Title } from "./layout"; import Icon from "./Icon"; type State = { error?: Error; errorInfo?: ErrorInfo; }; type ExportedProps = { fallback?: React.ComponentType; children: ReactNode; }; type Props = RouteComponentProps & ExportedProps; type ErrorInfo = { componentStack: string; }; type ErrorDisplayProps = { fallback?: React.ComponentType; error: Error; errorInfo: ErrorInfo; }; const RedirectIconContainer = styled.div` min-height: 256px; `; const RedirectPage = () => { const [t] = useTranslation("commons"); useDocumentTitle(t("errorNotification.prefix")); // we use an icon instead of loading spinner, // because a redirect is synchron and a spinner does not spin on a synchron action return (
{t("errorBoundary.redirect.title")} {t("errorBoundary.redirect.subtitle")}
); }; const ErrorDisplay: FC = ({ error, errorInfo, fallback: FallbackComponent }) => { const loginLink = useIndexLink("login"); const [t] = useTranslation("commons"); const location = useLocation(); const isMissingLink = error instanceof MissingLinkError; useEffect(() => { if (isMissingLink && loginLink) { window.location.assign(urls.withContextPath("/login?from=" + location.pathname)); } }, [isMissingLink, loginLink, location.pathname]); if (isMissingLink) { if (loginLink) { // we can render a loading screen, // because the effect hook above should redirect return ; } else { // missing link error without login link means we have no permissions // and we should render an error return ( ); } } if (!FallbackComponent) { return ; } const fallbackProps = { error, errorInfo, }; return ; }; class ErrorBoundary extends React.Component { constructor(props: Props) { super(props); this.state = {}; } componentDidUpdate(prevProps: Readonly) { // we must reset the error if the url has changed if (this.state.error && prevProps.location !== this.props.location) { this.setState({ error: undefined, errorInfo: undefined }); } } componentDidCatch(error: Error, errorInfo: ErrorInfo) { this.setState({ error, errorInfo, }); } render() { const { fallback } = this.props; const { error, errorInfo } = this.state; if (error && errorInfo) { return ; } return this.props.children; } } export default withRouter(ErrorBoundary);