Improve AppError boundaries (#11107)

## What

This PR aims to make sure all application exceptions are captured
through react-error-boundaries

Once merged we will have:
- Root Level: AppErrorBoundary at the highest level (full screen) ==>
this one needs to be working in any case, not relying on Theme, was not
working
- Route Level: AppErrorBoundary in DefaultLayout (full screen) ==> this
was missing and it seems that error are not propagated outside of the
router, making errors triggered in CommandMenu or NavigationDrawer
missing
- Page Level: AppErrorBoundary in DefaultLayout write around the Page
itself (lower than CommandMenu + NavigationDrawer)
- Manually triggered: example in ClientConfigProvider

## Screenshots

App level (ex throw in IconsProvider)
<img width="1512" alt="image"
src="https://github.com/user-attachments/assets/18a14815-a203-4edf-b931-43068c3436ec"
/>

Route level (ex throw in CommandMenu)
<img width="1512" alt="image"
src="https://github.com/user-attachments/assets/ca066627-14c7-438e-a432-f0999a1f3b84"
/>

Page level (ex throw in RecordTable)
<img width="1512" alt="image"
src="https://github.com/user-attachments/assets/ffeaa935-02af-4762-8859-7a0ccf8b77e1"
/>

Manually Triggered (clientConfig, ex when backend is not up)
<img width="1512" alt="image"
src="https://github.com/user-attachments/assets/062d6d84-097a-4ed9-b6ce-763b8c27c659"
/>
This commit is contained in:
Charles Bochet
2025-03-22 09:19:08 +01:00
committed by GitHub
parent c50cdd9510
commit 692e08f0d4
13 changed files with 341 additions and 162 deletions
@@ -0,0 +1,22 @@
import { useEffect, useState } from 'react';
import { FallbackProps } from 'react-error-boundary';
import { useLocation } from 'react-router-dom';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
type AppErrorBoundaryEffectProps = Pick<FallbackProps, 'resetErrorBoundary'>;
export const AppErrorBoundaryEffect = ({
resetErrorBoundary,
}: AppErrorBoundaryEffectProps) => {
const location = useLocation();
const [previousLocation] = useState(location);
useEffect(() => {
if (!isDeeplyEqual(previousLocation, location)) {
resetErrorBoundary();
}
}, [previousLocation, location, resetErrorBoundary]);
return <></>;
};
@@ -0,0 +1,34 @@
import { AppErrorDisplayProps } from '@/error-handler/types/AppErrorDisplayProps';
import {
AnimatedPlaceholder,
AnimatedPlaceholderEmptyContainer,
AnimatedPlaceholderEmptySubTitle,
AnimatedPlaceholderEmptyTextContainer,
AnimatedPlaceholderEmptyTitle,
Button,
IconRefresh,
} from 'twenty-ui';
export const AppErrorDisplay = ({
error,
resetErrorBoundary,
title = 'Sorry, something went wrong',
}: AppErrorDisplayProps) => {
return (
<AnimatedPlaceholderEmptyContainer>
<AnimatedPlaceholder type="errorIndex" />
<AnimatedPlaceholderEmptyTextContainer>
<AnimatedPlaceholderEmptyTitle>{title}</AnimatedPlaceholderEmptyTitle>
<AnimatedPlaceholderEmptySubTitle>
{error.message}
</AnimatedPlaceholderEmptySubTitle>
</AnimatedPlaceholderEmptyTextContainer>
<Button
Icon={IconRefresh}
title="Reload"
variant={'secondary'}
onClick={resetErrorBoundary}
/>
</AnimatedPlaceholderEmptyContainer>
);
};