mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
feat(console): add react error boundary
This commit is contained in:
parent
51f3b5c091
commit
9c3417c23a
3 changed files with 109 additions and 7 deletions
|
@ -2,6 +2,7 @@ import { useLogto } from '@logto/react';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { Outlet, useHref } from 'react-router-dom';
|
import { Outlet, useHref } from 'react-router-dom';
|
||||||
|
|
||||||
|
import ErrorBoundary from '../ErrorBoundary';
|
||||||
import Sidebar from './components/Sidebar';
|
import Sidebar from './components/Sidebar';
|
||||||
import Topbar from './components/Topbar';
|
import Topbar from './components/Topbar';
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
|
@ -36,15 +37,17 @@ const AppContent = ({ theme }: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.app}>
|
<ErrorBoundary>
|
||||||
<Topbar />
|
<div className={styles.app}>
|
||||||
<div className={styles.content}>
|
<Topbar />
|
||||||
<Sidebar />
|
<div className={styles.content}>
|
||||||
<div className={styles.main}>
|
<Sidebar />
|
||||||
<Outlet />
|
<div className={styles.main}>
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
@use '@/scss/underscore' as _;
|
||||||
|
|
||||||
|
.container {
|
||||||
|
overflow-y: auto;
|
||||||
|
height: 100vh;
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: var(--color-layer-1);
|
||||||
|
color: var(--color-text);
|
||||||
|
padding: _.unit(6);
|
||||||
|
width: 858px;
|
||||||
|
min-height: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
> *:not(:first-child) {
|
||||||
|
margin-top: _.unit(6);
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img,
|
||||||
|
h2 {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
details {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
65
packages/console/src/components/ErrorBoundary/index.tsx
Normal file
65
packages/console/src/components/ErrorBoundary/index.tsx
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import { conditional } from '@silverhand/essentials';
|
||||||
|
import React, { Component, ReactNode } from 'react';
|
||||||
|
import { Namespace, TFunction, withTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import ErrorImage from '@/assets/images/table-error.svg';
|
||||||
|
|
||||||
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: ReactNode;
|
||||||
|
t: TFunction<Namespace, 'admin_console'>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
callStack?: string;
|
||||||
|
componentStack?: string;
|
||||||
|
errorMessage?: string;
|
||||||
|
hasError: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ErrorBoundary extends Component<Props, State> {
|
||||||
|
static getDerivedStateFromError(error: Error) {
|
||||||
|
const errorMessage = conditional(
|
||||||
|
typeof error === 'object' && typeof error.message === 'string' && error.message
|
||||||
|
);
|
||||||
|
|
||||||
|
const callStack = conditional(
|
||||||
|
typeof error === 'object' &&
|
||||||
|
typeof error.stack === 'string' &&
|
||||||
|
error.stack.split('\n').slice(1).join('\n')
|
||||||
|
);
|
||||||
|
|
||||||
|
return { callStack, errorMessage, hasError: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
public state: State = {
|
||||||
|
callStack: undefined,
|
||||||
|
errorMessage: undefined,
|
||||||
|
hasError: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { children, t } = this.props;
|
||||||
|
const { callStack, errorMessage, hasError } = this.state;
|
||||||
|
|
||||||
|
if (hasError) {
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
<img src={ErrorImage} alt="oops" />
|
||||||
|
<h2>{t('errors.something_went_wrong')}</h2>
|
||||||
|
<details open>
|
||||||
|
<summary>{errorMessage}</summary>
|
||||||
|
{callStack}
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withTranslation()(ErrorBoundary);
|
Loading…
Reference in a new issue