Skip to content

Commit a7cffb4

Browse files
authored
Added fallback error page for unhandled errors in the posts app (#24402)
ref https://linear.app/ghost/issue/PROD-2313/unhandled-errors-in-the-stats-and-posts-apps-display-a-full-stack Currently unhandled errors in the posts app use the default react error page, which includes a full stack trace and is not friendly for users. This adds a generic fallback error boundary and error page, which matches the existing "Loading interrupted" screen that is shown if the app can't be loaded. Users should never see this screen, but just in case it's better to have something more user friendly like this.
1 parent f0f8a36 commit a7cffb4

File tree

3 files changed

+47
-3
lines changed

3 files changed

+47
-3
lines changed

apps/posts/src/App.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import PostsErrorBoundary from './components/errors/PostsErrorBoundary';
12
import React, {createContext, useContext} from 'react';
23
import {APP_ROUTE_PREFIX, routes} from '@src/routes';
34
import {AppContextType, BaseAppProps, FrameworkProvider, Outlet, RouterProvider} from '@tryghost/admin-x-framework';
@@ -41,9 +42,11 @@ const App: React.FC<AppProps> = ({framework, designSystem, fromAnalytics = false
4142
>
4243
<PostsAppContext.Provider value={appContextValue}>
4344
<RouterProvider prefix={APP_ROUTE_PREFIX} routes={routes}>
44-
<ShadeApp className="shade-posts" darkMode={designSystem.darkMode} fetchKoenigLexical={null}>
45-
<Outlet />
46-
</ShadeApp>
45+
<PostsErrorBoundary>
46+
<ShadeApp className="shade-posts" darkMode={designSystem.darkMode} fetchKoenigLexical={null}>
47+
<Outlet />
48+
</ShadeApp>
49+
</PostsErrorBoundary>
4750
</RouterProvider>
4851
</PostsAppContext.Provider>
4952
</FrameworkProvider>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import PostsErrorPage from './PostsErrorPage';
2+
import React from 'react';
3+
4+
class PostsErrorBoundary extends React.Component<{children: React.ReactNode}, {hasError: boolean, error?: Error}> {
5+
constructor(props: {children: React.ReactNode}) {
6+
super(props);
7+
this.state = {hasError: false};
8+
}
9+
10+
static getDerivedStateFromError(error: Error) {
11+
return {hasError: true, error};
12+
}
13+
14+
render() {
15+
if (this.state.hasError) {
16+
return <PostsErrorPage error={this.state.error} />;
17+
}
18+
return this.props.children;
19+
}
20+
}
21+
22+
export default PostsErrorBoundary;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React from 'react';
2+
3+
interface PostsErrorPageProps {
4+
error?: Error;
5+
}
6+
7+
const PostsErrorPage: React.FC<PostsErrorPageProps> = () => {
8+
return (
9+
<div className="admin-x-container-error">
10+
<div className="admin-x-error">
11+
<h1>Loading interrupted</h1>
12+
<p>They say life is a series of trials and tribulations. This moment right here? It&apos;s a tribulation. Our app was supposed to load, and yet here we are. Loadless. Click back to the dashboard to try again.</p>
13+
<a className="cursor-pointer" onClick={() => window.location.reload()}>&larr; Back to the dashboard</a>
14+
</div>
15+
</div>
16+
);
17+
};
18+
19+
export default PostsErrorPage;

0 commit comments

Comments
 (0)