Add error handling component which supports React Router and Kernel applications.

This commit is contained in:
Madeorsk 2024-07-14 12:23:16 +02:00
parent 7bde352ea7
commit 4a7e77a8f3
Signed by: Madeorsk
SSH key fingerprint: SHA256:J9G0ofIOLKf7kyS2IfrMqtMaPdfsk1W02+oGueZzDDU
5 changed files with 166 additions and 4 deletions

View file

@ -5,6 +5,7 @@ import {createBrowserRouter} from "react-router-dom";
import {Kernel} from "../src/Application/Kernel"; import {Kernel} from "../src/Application/Kernel";
import {NavTest} from "./NavTest"; import {NavTest} from "./NavTest";
import {Avocado} from "@phosphor-icons/react"; import {Avocado} from "@phosphor-icons/react";
import {ApplicationError} from "../src/Application/ApplicationError";
// Router initialization. // Router initialization.
const router = createBrowserRouter([ const router = createBrowserRouter([
@ -17,6 +18,7 @@ const router = createBrowserRouter([
element: <NavTest />, element: <NavTest />,
} }
], ],
errorElement: <ApplicationError />,
} }
]) ])

View file

@ -1,13 +1,18 @@
import React from "react"; import React from "react";
import {ApplicationError, ApplicationErrorBoundary} from "./ApplicationError";
/** /**
* Main Kernel UI application. * Main Kernel UI application.
*/ */
export function Application({children}: React.PropsWithChildren<{}>) export function Application({errorElement, children}: React.PropsWithChildren<{
errorElement?: React.ReactNode;
}>)
{ {
return ( return (
<main className={"app"}> <ApplicationErrorBoundary errorElement={errorElement ?? <ApplicationError />}>
{children} <main className={"app"}>
</main> {children}
</main>
</ApplicationErrorBoundary>
); );
} }

View file

@ -0,0 +1,94 @@
import React, {useContext, useState} from "react";
import {ArrowsClockwise, Bug, BugDroid} from "@phosphor-icons/react";
import {useRouteError} from "react-router-dom";
/**
* Application error context.
*/
const ApplicationErrorContext = React.createContext<Error>(undefined);
/**
* Get current error from context or router.
*/
export function useApplicationError(): Error
{
// Get error from context or router.
const error = useContext(ApplicationErrorContext);
const routeError = useRouteError() as Error;
return error ?? routeError;
}
/**
* Application error component.
*/
export function ApplicationError({children}: {
children?: (error: Error) => React.ReactElement;
})
{
// Get error from context.
const error = useApplicationError();
// Show details state.
const [showDetails, setShowDetails] = useState(false);
return (
<main className={"error"}>
{children ? children(error) : (
<>
<Bug size={64} weight={"duotone"} />
<h1>Error</h1>
<hr />
<h2>{error.name}</h2>
<p>An unexpected error happened and the application was forced to quit.</p>
<pre className={"error"}>{error.message}</pre>
<button onClick={() => window.location.reload()}><ArrowsClockwise size={20} /> Restart application</button>
<div className={"details"}>
<button onClick={() => setShowDetails(!showDetails)}><BugDroid size={18} /> {showDetails ? "Hide" : "Show"} details</button>
{ // Show details if required.
showDetails && (
<pre>
{error.stack}
</pre>
)
}
</div>
</>
)}
</main>
);
}
/**
* Error boundary component for the application.
*/
export class ApplicationErrorBoundary extends React.Component<React.PropsWithChildren<{
errorElement: React.ReactNode;
}>, {
error: Error;
}>
{
static getDerivedStateFromError(error: Error)
{
return { error: error };
}
render()
{
if (this.state?.error)
// An error happened, showing the application error content.
return (
<ApplicationErrorContext.Provider value={this.state?.error}>
{this.props.errorElement}
</ApplicationErrorContext.Provider>
);
return this.props.children;
}
}

View file

@ -2,6 +2,7 @@
@import "components/_button"; @import "components/_button";
@import "components/_card"; @import "components/_card";
@import "components/_dates"; @import "components/_dates";
@import "components/_errors";
@import "components/_floating"; @import "components/_floating";
@import "components/_form"; @import "components/_form";
@import "components/_headings"; @import "components/_headings";

View file

@ -0,0 +1,60 @@
main.error
{
font-size: 1.2em;
> svg
{
display: block;
margin: 1em auto;
color: var(--red);
}
hr
{
margin: 1em auto;
width: 20em;
max-width: 50%;
height: 0.25em;
border-radius: 0.1em;
border: none;
background: var(--red);
}
h1, h2, h3, h4, h5, h6
{
margin: auto;
text-align: center;
}
p, pre
{
margin: 1em auto;
width: 40em;
max-width: 95%;
text-align: center;
}
button
{
display: block;
margin: 1em auto;
padding: 0.66em 1em;
}
.details
{
font-size: 0.8em;
button
{}
pre
{
width: auto;
text-align: left;
}
}
}