/* * Copyright (C) 2022 Kristóf Marussy * * This file is part of Sophie. * * Sophie 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 . * * SPDX-License-Identifier: AGPL-3.0-only */ import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; import NoEncryptionIcon from '@mui/icons-material/NoEncryptionOutlined'; import SentimentVeryDissatisfiedIcon from '@mui/icons-material/SentimentVeryDissatisfied'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import Typography from '@mui/material/Typography'; import type { TFunction } from 'i18next'; import { observer } from 'mobx-react-lite'; import React from 'react'; import { useTranslation } from 'react-i18next'; import type Service from '../../stores/Service'; import CertificateDetails from './CertificateDetails'; interface ErrorDetails { icon: JSX.Element; title: string; description: string; errorCode: string | undefined; } function formatError(service: Service, t: TFunction): ErrorDetails { const { settings: { name: serviceName }, state, } = service; const { type } = state; switch (type) { case 'failed': { const { errorCode, errorDesc } = state; const errorCodeStr = errorCode.toString(10); return { icon: , title: t( [ `error.failed.${errorCodeStr}.title`, 'error.failed.unspecific.title', ], { serviceName }, ), description: t( [ `error.failed.${errorCodeStr}.description`, 'error.failed.unspecific.description', ], { serviceName }, ), errorCode: errorDesc.length > 0 ? `${errorDesc} (${errorCodeStr})` : errorCodeStr, }; } case 'certificateError': { const { errorCode } = state; // Avoid i18next namespace separators in the error code. const errorCodeSafe = errorCode.replaceAll(':', '_'); return { icon: , title: t( [ `error.certificateError.${errorCodeSafe}.title`, 'error.certificateError.unspecific.title', ], { serviceName }, ), description: t( [ `error.certificateError.${errorCodeSafe}.description`, 'error.certificateError.unspecific.description', ], { serviceName }, ), errorCode, }; } case 'crashed': { const { reason, exitCode } = state; return { icon: , title: t( [`error.crashed.${reason}.title`, 'error.crashed.unspecific.title'], { serviceName }, ), description: t( [ `error.crashed.${reason}.description`, 'error.crashed.unspecific.description', ], { serviceName }, ), errorCode: `${reason} (${exitCode})`, }; } default: return { icon: , title: t('error.unknown.title', { serviceName }), description: t('error.unknown.description', { serviceName }), errorCode: undefined, }; } } function ErrorPage({ service }: { service: Service }): JSX.Element | null { const { t } = useTranslation(undefined); if (!service.hasError) { // eslint-disable-next-line unicorn/no-null -- React requires `null` to skip rendering. return null; } const { settings: { name: serviceName }, state: { type: errorType }, } = service; const { icon, title, description, errorCode } = formatError(service, t); return ( {icon} {title} {description} {errorCode !== undefined && ( {t('error.errorCode', { errorCode })} )} {errorType !== 'certificateError' && ( )} ); } export default observer(ErrorPage);