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:
@@ -0,0 +1,131 @@
|
||||
import { AppErrorDisplayProps } from '@/error-handler/types/AppErrorDisplayProps';
|
||||
import styled from '@emotion/styled';
|
||||
import LightNoise from '@ui/theme/assets/light-noise.png';
|
||||
import { motion } from 'framer-motion';
|
||||
import { GRAY_SCALE, IconReload } from 'twenty-ui';
|
||||
|
||||
type AppRootErrorFallbackProps = AppErrorDisplayProps;
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
background: url(${LightNoise.toString()});
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
padding: 12px;
|
||||
`;
|
||||
|
||||
const StyledPanel = styled.div`
|
||||
background: ${GRAY_SCALE.gray0};
|
||||
border: 1px solid ${GRAY_SCALE.gray20};
|
||||
border-radius: 8px;
|
||||
height: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledEmptyContainer = styled(motion.div)`
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const StyledImageContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const StyledBackgroundImage = styled.img`
|
||||
max-height: 160px;
|
||||
max-width: 160px;
|
||||
`;
|
||||
|
||||
const StyledInnerImage = styled.img`
|
||||
max-height: 130px;
|
||||
position: absolute;
|
||||
max-width: 130px;
|
||||
`;
|
||||
|
||||
const StyledEmptyTextContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledEmptyTitle = styled.div`
|
||||
color: ${GRAY_SCALE.gray60};
|
||||
font-size: 1.23rem;
|
||||
font-weight: 600;
|
||||
`;
|
||||
|
||||
const StyledEmptySubTitle = styled.div`
|
||||
color: ${GRAY_SCALE.gray50};
|
||||
font-size: 0.92rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
max-height: 2.8em;
|
||||
overflow: hidden;
|
||||
width: 50%;
|
||||
`;
|
||||
|
||||
const StyledButton = styled.button`
|
||||
align-items: center;
|
||||
background: ${GRAY_SCALE.gray0};
|
||||
border: 1px solid ${GRAY_SCALE.gray20};
|
||||
color: ${GRAY_SCALE.gray60};
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
padding: 8px 16px;
|
||||
padding: 8px;
|
||||
`;
|
||||
|
||||
const StyledIcon = styled(IconReload)`
|
||||
color: ${GRAY_SCALE.gray60};
|
||||
margin-right: 8px;
|
||||
`;
|
||||
|
||||
export const AppRootErrorFallback = ({
|
||||
error,
|
||||
resetErrorBoundary,
|
||||
title = 'Sorry, something went wrong',
|
||||
}: AppRootErrorFallbackProps) => {
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledPanel>
|
||||
<StyledEmptyContainer>
|
||||
<StyledImageContainer>
|
||||
<StyledBackgroundImage
|
||||
src="/images/placeholders/background/error_index_bg.png"
|
||||
alt="Background"
|
||||
/>
|
||||
<StyledInnerImage
|
||||
src="/images/placeholders/moving-image/error_index.png"
|
||||
alt="Inner"
|
||||
/>
|
||||
</StyledImageContainer>
|
||||
<StyledEmptyTextContainer>
|
||||
<StyledEmptyTitle>{title}</StyledEmptyTitle>
|
||||
<StyledEmptySubTitle>{error.message}</StyledEmptySubTitle>
|
||||
</StyledEmptyTextContainer>
|
||||
<StyledButton onClick={resetErrorBoundary}>
|
||||
<StyledIcon size={16} />
|
||||
Reload
|
||||
</StyledButton>
|
||||
</StyledEmptyContainer>
|
||||
</StyledPanel>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user