init commit,
This commit is contained in:
168
03_source/frontend/src/routes/components/error-boundary.tsx
Normal file
168
03_source/frontend/src/routes/components/error-boundary.tsx
Normal file
@@ -0,0 +1,168 @@
|
||||
import type { Theme, CSSObject } from '@mui/material/styles';
|
||||
|
||||
import { useRouteError, isRouteErrorResponse } from 'react-router';
|
||||
|
||||
import GlobalStyles from '@mui/material/GlobalStyles';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export function ErrorBoundary() {
|
||||
const error = useRouteError();
|
||||
|
||||
return (
|
||||
<>
|
||||
{inputGlobalStyles()}
|
||||
|
||||
<div className={errorBoundaryClasses.root}>
|
||||
<div className={errorBoundaryClasses.container}>{renderErrorMessage(error)}</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
function parseStackTrace(stack?: string) {
|
||||
if (!stack) return { filePath: null, functionName: null };
|
||||
|
||||
const filePathMatch = stack.match(/\/src\/[^?]+/);
|
||||
const functionNameMatch = stack.match(/at (\S+)/);
|
||||
|
||||
return {
|
||||
filePath: filePathMatch ? filePathMatch[0] : null,
|
||||
functionName: functionNameMatch ? functionNameMatch[1] : null,
|
||||
};
|
||||
}
|
||||
|
||||
function renderErrorMessage(error: any) {
|
||||
if (isRouteErrorResponse(error)) {
|
||||
return (
|
||||
<>
|
||||
<h1 className={errorBoundaryClasses.title}>
|
||||
{error.status}: {error.statusText}
|
||||
</h1>
|
||||
<p className={errorBoundaryClasses.message}>{error.data}</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
const { filePath, functionName } = parseStackTrace(error.stack);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1 className={errorBoundaryClasses.title}>Unexpected Application Error!</h1>
|
||||
<p className={errorBoundaryClasses.message}>
|
||||
{error.name}: {error.message}
|
||||
</p>
|
||||
<pre className={errorBoundaryClasses.details}>{error.stack}</pre>
|
||||
{(filePath || functionName) && (
|
||||
<p className={errorBoundaryClasses.filePath}>
|
||||
{filePath} ({functionName})
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return <h1 className={errorBoundaryClasses.title}>Unknown Error</h1>;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const errorBoundaryClasses = {
|
||||
root: 'error-boundary-root',
|
||||
container: 'error-boundary-container',
|
||||
title: 'error-boundary-title',
|
||||
details: 'error-boundary-details',
|
||||
message: 'error-boundary-message',
|
||||
filePath: 'error-boundary-file-path',
|
||||
};
|
||||
|
||||
const cssVars: CSSObject = {
|
||||
'--info-color': '#2dd9da',
|
||||
'--warning-color': '#e2aa53',
|
||||
'--error-color': '#ff5555',
|
||||
'--error-background': '#2a1e1e',
|
||||
'--details-background': '#111111',
|
||||
'--root-background': '#2c2c2e',
|
||||
'--container-background': '#1c1c1e',
|
||||
'--font-stack-monospace':
|
||||
'"SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace',
|
||||
'--font-stack-sans':
|
||||
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"',
|
||||
};
|
||||
|
||||
const rootStyles = (): CSSObject => ({
|
||||
display: 'flex',
|
||||
flex: '1 1 auto',
|
||||
alignItems: 'center',
|
||||
padding: '10vh 15px 0',
|
||||
flexDirection: 'column',
|
||||
fontFamily: 'var(--font-stack-sans)',
|
||||
});
|
||||
|
||||
const contentStyles = (): CSSObject => ({
|
||||
gap: 24,
|
||||
padding: 20,
|
||||
width: '100%',
|
||||
maxWidth: 960,
|
||||
display: 'flex',
|
||||
borderRadius: 8,
|
||||
flexDirection: 'column',
|
||||
backgroundColor: 'var(--container-background)',
|
||||
});
|
||||
|
||||
const titleStyles = (theme: Theme): CSSObject => ({
|
||||
margin: 0,
|
||||
lineHeight: 1.2,
|
||||
fontSize: theme.typography.pxToRem(20),
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
});
|
||||
|
||||
const messageStyles = (theme: Theme): CSSObject => ({
|
||||
margin: 0,
|
||||
lineHeight: 1.5,
|
||||
padding: '12px 16px',
|
||||
whiteSpace: 'pre-wrap',
|
||||
color: 'var(--error-color)',
|
||||
fontSize: theme.typography.pxToRem(14),
|
||||
fontFamily: 'var(--font-stack-monospace)',
|
||||
backgroundColor: 'var(--error-background)',
|
||||
borderLeft: '2px solid var(--error-color)',
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
});
|
||||
|
||||
const detailsStyles = (): CSSObject => ({
|
||||
margin: 0,
|
||||
padding: 16,
|
||||
lineHeight: 1.5,
|
||||
overflow: 'auto',
|
||||
borderRadius: 'inherit',
|
||||
color: 'var(--warning-color)',
|
||||
backgroundColor: 'var(--details-background)',
|
||||
});
|
||||
|
||||
const filePathStyles = (): CSSObject => ({
|
||||
marginTop: 0,
|
||||
color: 'var(--info-color)',
|
||||
});
|
||||
|
||||
const inputGlobalStyles = () => (
|
||||
<GlobalStyles
|
||||
styles={(theme) => ({
|
||||
body: {
|
||||
...cssVars,
|
||||
margin: 0,
|
||||
color: 'white',
|
||||
backgroundColor: 'var(--root-background)',
|
||||
[`& .${errorBoundaryClasses.root}`]: rootStyles(),
|
||||
[`& .${errorBoundaryClasses.container}`]: contentStyles(),
|
||||
[`& .${errorBoundaryClasses.title}`]: titleStyles(theme),
|
||||
[`& .${errorBoundaryClasses.message}`]: messageStyles(theme),
|
||||
[`& .${errorBoundaryClasses.filePath}`]: filePathStyles(),
|
||||
[`& .${errorBoundaryClasses.details}`]: detailsStyles(),
|
||||
},
|
||||
})}
|
||||
/>
|
||||
);
|
Reference in New Issue
Block a user