All files / src/components/ErrorBoundary ErrorBoundary.tsx

100% Statements 14/14
100% Branches 4/4
100% Functions 8/8
100% Lines 13/13

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81                                        31x 30x       4x       2x     30x 1x             33x 33x 4x                               1x                       29x 29x               5x  
import React, { type ReactNode } from 'react';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button';
import { withTranslation, type WithTranslation } from 'react-i18next';
import { logError } from '../../utils/logger';
import Seo from '../Seo';
import { PageWrapper } from '../PageWrapper';
 
interface Props extends WithTranslation {
  children: ReactNode;
}
 
interface State {
  hasError: boolean;
  reloadKey: number;
}
 
class ErrorBoundaryInner extends React.Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = { hasError: false, reloadKey: 0 };
  }
 
  static getDerivedStateFromError(_: Error): Pick<State, 'hasError'> {
    return { hasError: true };
  }
 
  componentDidCatch(error: Error, info: React.ErrorInfo) {
    logError('ErrorBoundary', { error, info });
  }
 
  handleReload = () => {
    this.setState(({ reloadKey }) => ({
      hasError: false,
      reloadKey: reloadKey + 1,
    }));
  };
 
  render() {
    const { t, children } = this.props;
    if (this.state.hasError) {
      return (
        <>
          <Seo title={t('errors.seoTitle')} description={t('errors.seoDescription')} />
          <PageWrapper>
            <Box sx={{ p: 4, textAlign: 'center' }}>
              <Typography variant="h4" gutterBottom>
                {t('errors.title')}
              </Typography>
              <Typography variant="h5" sx={{ mb: 2 }}>
                {t('errors.unexpected')}
              </Typography>
              <Button variant="contained" onClick={this.handleReload}>
                {t('errors.retry')}
              </Button>
              <Button
                variant="text"
                onClick={() => (window.location.href = '/')}
                sx={{ ml: 2 }}
              >
                {t('errors.home')}
              </Button>
            </Box>
          </PageWrapper>
        </>
      );
    }
 
    // On donne une nouvelle key quand on recharge pour « reset » les enfants
    return React.Children.map(children, (child) =>
      React.isValidElement(child)
        ? React.cloneElement(child, { key: `reload-${this.state.reloadKey}` })
        : child
    );
  }
}
 
// on wrappe avec withTranslation pour injecter `t`
export const ErrorBoundary = withTranslation()(ErrorBoundaryInner);
export { ErrorBoundaryInner };