Skip to content

Commit

Permalink
feat: add Error Boundary with debugging info
Browse files Browse the repository at this point in the history
  • Loading branch information
jakeaturner committed Oct 25, 2023
1 parent 786b03b commit d610569
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 46 deletions.
20 changes: 20 additions & 0 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"react-datepicker": "^4.12.0",
"react-day-picker": "^8.6.0",
"react-dom": "^17.0.2",
"react-error-boundary": "^4.0.11",
"react-hook-form": "^7.43.5",
"react-mentions": "^4.3.0",
"react-minimal-pie-chart": "^8.2.0",
Expand Down
105 changes: 59 additions & 46 deletions client/src/Platform.jsx
Original file line number Diff line number Diff line change
@@ -1,64 +1,77 @@
import React from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import { Provider } from 'react-redux';
import axios from 'axios';
import store from './state/store.ts';
import AuthHelper from './components/util/AuthHelper.js';
import Commons from './Commons';
import Conductor from './Conductor';
import Standalone from './Standalone';
import ErrorModal from './components/error/ErrorModal';
import withOrgStateDependency from './enhancers/withOrgStateDependency';
import './styles/global.css';
import React from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import { Provider } from "react-redux";
import axios from "axios";
import store from "./state/store.ts";
import AuthHelper from "./components/util/AuthHelper.js";
import Commons from "./Commons";
import Conductor from "./Conductor";
import Standalone from "./Standalone";
import ErrorModal from "./components/error/ErrorModal";
import withOrgStateDependency from "./enhancers/withOrgStateDependency";
import "./styles/global.css";
import { ErrorBoundary } from "react-error-boundary";
import ErrorScreen from "./screens/ErrorScreen";

/**
* Exposes the applications and global configuration.
*/
const Platform = () => {
/* Configure global Axios defaults */
axios.defaults.baseURL = (import.meta.env.MODE === 'development') ? `${import.meta.env.VITE_DEV_BASE_URL}/api/v1` : '/api/v1';
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
axios.defaults.headers.post['Content-Type'] = 'application/json';
axios.defaults.baseURL =
import.meta.env.MODE === "development"
? `${import.meta.env.VITE_DEV_BASE_URL}/api/v1`
: "/api/v1";
axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";
axios.defaults.headers.post["Content-Type"] = "application/json";
axios.defaults.withCredentials = true;
axios.interceptors.response.use((res) => {
return res;
}, (err) => {
if (err.response?.status === 401 && err.response?.data?.tokenExpired === true) {
AuthHelper.logout(true, window.location);
axios.interceptors.response.use(
(res) => {
return res;
},
(err) => {
if (
err.response?.status === 401 &&
err.response?.data?.tokenExpired === true
) {
AuthHelper.logout(true, window.location);
}
return Promise.reject(err);
}
return Promise.reject(err);
});
);
/* Set up render trees */
const commonsPaths = [
'/',
'/catalog',
'/collections',
'/collection/:id',
'/book/:id',
'/homework',
'/underdevelopment',
'/libraries'
"/",
"/catalog",
"/collections",
"/collection/:id",
"/book/:id",
"/homework",
"/underdevelopment",
"/libraries",
];
const standalonePaths = [
'/adopt',
'/accessibility',
'/translationfeedbackexport',
'/oauthconsent'
"/adopt",
"/accessibility",
"/translationfeedbackexport",
"/oauthconsent",
];
const ApplicationTree = () => {
return (
<div className='App'>
<Switch>
{/* Commons Render Tree */}
<Route exact path={commonsPaths} component={Commons} />
{/* Standalone Pages */}
<Route exact path={standalonePaths} component={Standalone} />
{/* Conductor and fallback Render Tree */}
<Route component={Conductor} />
</Switch>
<ErrorModal />
</div>
)
<ErrorBoundary FallbackComponent={ErrorScreen}>
<div className="App">
<Switch>
{/* Commons Render Tree */}
<Route exact path={commonsPaths} component={Commons} />
{/* Standalone Pages */}
<Route exact path={standalonePaths} component={Standalone} />
{/* Conductor and fallback Render Tree */}
<Route component={Conductor} />
</Switch>
<ErrorModal />
</div>
</ErrorBoundary>
);
};
/* Require Organization info globally */
const Application = withOrgStateDependency(ApplicationTree);
Expand Down
90 changes: 90 additions & 0 deletions client/src/screens/ErrorScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import {
Message,
Icon,
Button,
Image,
Accordion,
} from "semantic-ui-react";
import { useEffect, useState } from "react";

const ErrorScreen = ({
error,
resetErrorBoundary,
}: {
error: any;
resetErrorBoundary: () => void;
}) => {
const [devInfoOpen, setDevInfoOpen] = useState(false);

/**
* Setup page & title on load
*/
useEffect(() => {
document.title = "LibreTexts | Error";
}, []);

return (
<div className="flex flex-col items-center bg-white h-screen w-screen justify-center">
<div className="flex flex-col items-center justify-center py-4 px-36">
<Image
src="/libretexts_logo.png"
alt="LibreTexts logo"
className="w-96"
/>
<p className="text-3xl font-semibold mb-8 mt-10 text-center">
Oops, this page encountered an error. Please refresh to try again.
</p>
<Button
icon
color="blue"
size="big"
className="flex mt-4"
onClick={() => window.location.reload()}
>
<Icon name="refresh" /> Refresh
</Button>
<Accordion styled className="!w-80 mt-24">
<Accordion.Title
active={devInfoOpen}
index={0}
onClick={() => setDevInfoOpen(!devInfoOpen)}
>
<Icon name="dropdown" />
Show Debugging Info
</Accordion.Title>
<Accordion.Content active={devInfoOpen}>
{typeof error === "object" && error.message && (
<Message negative>
<Message.Header>{error.name}</Message.Header>
<p>{error.message}</p>
</Message>
)}
{typeof error === "string" && (
<Message negative>
<Message.Header>Unknown Error</Message.Header>
<p>{error}</p>
</Message>
)}
{!error && (
<Message negative>
<Message.Header>Unknown Error</Message.Header>
<p>No error message was provided.</p>
</Message>
)}
</Accordion.Content>
</Accordion>
<Button
icon
size="mini"
className="flex !mt-6"
onClick={() => window.location.assign("https://launchpad.libretexts.org")}
>
<Icon name="location arrow" /> Go To Launchpad
</Button>

</div>
</div>
);
};

export default ErrorScreen;

0 comments on commit d610569

Please sign in to comment.