diff --git a/packages/console/src/App.tsx b/packages/console/src/App.tsx
index b3261f093..edcbb41c9 100644
--- a/packages/console/src/App.tsx
+++ b/packages/console/src/App.tsx
@@ -17,6 +17,7 @@ import initI18n from '@/i18n/init';
import { adminTenantEndpoint } from './consts';
import { isCloud } from './consts/cloud';
+import ErrorBoundary from './containers/ErrorBoundary';
import AppConfirmModalProvider from './contexts/AppConfirmModalProvider';
import AppEndpointsProvider from './contexts/AppEndpointsProvider';
import TenantsProvider, { TenantsContext } from './contexts/TenantsProvider';
@@ -68,9 +69,11 @@ const Content = () => {
const App = () => {
return (
-
-
-
+
+
+
+
+
);
};
export default App;
diff --git a/packages/console/src/components/AppError/index.tsx b/packages/console/src/components/AppError/index.tsx
index c97d0d35c..38e3f6aae 100644
--- a/packages/console/src/components/AppError/index.tsx
+++ b/packages/console/src/components/AppError/index.tsx
@@ -6,8 +6,8 @@ import ErrorDark from '@/assets/images/error-dark.svg';
import Error from '@/assets/images/error.svg';
import KeyboardArrowDown from '@/assets/images/keyboard-arrow-down.svg';
import KeyboardArrowUp from '@/assets/images/keyboard-arrow-up.svg';
-import { useTheme } from '@/hooks/use-theme';
import { onKeyDownHandler } from '@/utils/a11y';
+import { getThemeFromLocalStorage } from '@/utils/theme';
import * as styles from './index.module.scss';
@@ -22,7 +22,7 @@ type Props = {
const AppError = ({ title, errorCode, errorMessage, callStack, children }: Props) => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const [isDetailsOpen, setIsDetailsOpen] = useState(false);
- const theme = useTheme();
+ const theme = getThemeFromLocalStorage(); // Should be able to use the component in an offline environment
return (
diff --git a/packages/console/src/components/AppLoading/Offline.tsx b/packages/console/src/components/AppLoading/Offline.tsx
index 88a13fdea..12e3b0f5a 100644
--- a/packages/console/src/components/AppLoading/Offline.tsx
+++ b/packages/console/src/components/AppLoading/Offline.tsx
@@ -1,12 +1,7 @@
-import { AppearanceMode } from '@logto/schemas';
-import { trySafe } from '@silverhand/essentials';
-import { z } from 'zod';
-
import IllustrationDark from '@/assets/images/loading-illustration-dark.svg';
import Illustration from '@/assets/images/loading-illustration.svg';
import { Daisy as Spinner } from '@/components/Spinner';
-import { themeStorageKey } from '@/consts';
-import { getTheme } from '@/utils/theme';
+import { getThemeFromLocalStorage } from '@/utils/theme';
import * as styles from './index.module.scss';
@@ -14,10 +9,7 @@ import * as styles from './index.module.scss';
* An fullscreen loading component fetches local stored theme without sending request.
*/
export const AppLoadingOffline = () => {
- const theme = getTheme(
- trySafe(() => z.nativeEnum(AppearanceMode).parse(localStorage.getItem(themeStorageKey))) ??
- AppearanceMode.SyncWithSystem
- );
+ const theme = getThemeFromLocalStorage();
return (
diff --git a/packages/console/src/containers/ErrorBoundary/index.tsx b/packages/console/src/containers/ErrorBoundary/index.tsx
index e2fbdf779..7f5a75de9 100644
--- a/packages/console/src/containers/ErrorBoundary/index.tsx
+++ b/packages/console/src/containers/ErrorBoundary/index.tsx
@@ -16,10 +16,8 @@ type State = {
};
class ErrorBoundary extends Component
{
- static getDerivedStateFromError(error: Error) {
- const errorMessage = conditional(
- typeof error === 'object' && typeof error.message === 'string' && error.message
- );
+ static getDerivedStateFromError(error: Error): State {
+ const errorMessage = String(error);
const callStack = conditional(
typeof error === 'object' &&
@@ -36,6 +34,24 @@ class ErrorBoundary extends Component {
hasError: false,
};
+ promiseRejectionHandler(error: unknown) {
+ this.setState(
+ ErrorBoundary.getDerivedStateFromError(
+ error instanceof Error ? error : new Error(String(error))
+ )
+ );
+ }
+
+ componentDidMount(): void {
+ window.addEventListener('unhandledrejection', (event) => {
+ this.promiseRejectionHandler(event.reason);
+ });
+ }
+
+ componentWillUnmount(): void {
+ window.removeEventListener('unhandledrejection', this.promiseRejectionHandler);
+ }
+
render() {
const { children } = this.props;
const { callStack, errorMessage, hasError } = this.state;
diff --git a/packages/console/src/hooks/use-swr-options.ts b/packages/console/src/hooks/use-swr-options.ts
index cb2c0a0fe..486640dd8 100644
--- a/packages/console/src/hooks/use-swr-options.ts
+++ b/packages/console/src/hooks/use-swr-options.ts
@@ -4,7 +4,7 @@ import useApi, { RequestError } from './use-api';
import useSwrFetcher from './use-swr-fetcher';
const useSwrOptions = (): SWRConfiguration => {
- const api = useApi({ hideErrorToast: true });
+ const api = useApi();
const fetcher = useSwrFetcher(api);
return {
diff --git a/packages/console/src/utils/theme.ts b/packages/console/src/utils/theme.ts
index b1598ea51..f4590be46 100644
--- a/packages/console/src/utils/theme.ts
+++ b/packages/console/src/utils/theme.ts
@@ -1,4 +1,8 @@
import { AppearanceMode } from '@logto/schemas';
+import { trySafe } from '@silverhand/essentials';
+import { z } from 'zod';
+
+import { themeStorageKey } from '@/consts';
export const getTheme = (appearanceMode: AppearanceMode) => {
if (appearanceMode !== AppearanceMode.SyncWithSystem) {
@@ -10,3 +14,9 @@ export const getTheme = (appearanceMode: AppearanceMode) => {
return theme;
};
+
+export const getThemeFromLocalStorage = () =>
+ getTheme(
+ trySafe(() => z.nativeEnum(AppearanceMode).parse(localStorage.getItem(themeStorageKey))) ??
+ AppearanceMode.SyncWithSystem
+ );