Skip to content

Commit

Permalink
Merge pull request #140 from Olog/CSSTUDIO-2002-handle-404
Browse files Browse the repository at this point in the history
CSSTUDIO-2002: handle app errors and unknown routes
  • Loading branch information
cjenkscybercom authored Mar 21, 2024
2 parents f61a1d8 + 479faed commit 4a16587
Show file tree
Hide file tree
Showing 21 changed files with 219 additions and 103 deletions.
24 changes: 22 additions & 2 deletions 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 package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"react": "^18.1.0",
"react-datetime-picker": "^3.0.4",
"react-dom": "^18.1.0",
"react-error-boundary": "^4.0.13",
"react-focus-lock": "^2.9.2",
"react-hook-form": "^7.46.1",
"react-hook-form-persist": "^3.0.0",
Expand Down
2 changes: 1 addition & 1 deletion src/beta/components/navigation/AppNavBar/AppNavBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import { AppBar, Toolbar, Typography, Button, Stack, IconButton, List, ListItem } from "@mui/material";
import { Link as RouterLink } from "react-router-dom";
import Initialize from "components/Initialize";
import { InternalButtonLink } from "components/shared/InternalLink";
import { InternalButtonLink } from "components/shared/Link";
import { useShowLogin, useUser } from "features/authSlice";
import React from "react";
import packageInfo from "../../../../../package.json";
Expand Down
2 changes: 1 addition & 1 deletion src/components/Banner/Banner.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import LogoutDialog from '../LoginLogout/LogoutDialog';
import packageInfo from '../../../package.json';
import SkipToContent from 'components/shared/SkipToContent';
import { AppBar, Toolbar, Typography, Button, Link as MuiLink, List, ListItem, Stack, Box } from '@mui/material';
import { InternalButtonLink } from "components/shared/InternalLink";
import { InternalButtonLink } from "components/shared/Link";
import { useShowLogin, useShowLogout, useUser } from "features/authSlice";
import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome';
import customization from "config/customization";
Expand Down
2 changes: 1 addition & 1 deletion src/components/LogDetails/LogDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import LogEntrySingleView from './LogEntrySingleView';
import { useHref, useLocation } from "react-router-dom";
import NavigationButtons from './NavigationButtons';
import { Box, Button, Divider, Stack, styled } from '@mui/material';
import { InternalButtonLink } from 'components/shared/InternalLink';
import { InternalButtonLink } from 'components/shared/Link';
import { useUser } from 'features/authSlice';

const StyledLogEntrySingleView = styled(LogEntrySingleView)`
Expand Down
2 changes: 1 addition & 1 deletion src/components/log/EntryEditor/Description/Description.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import OlogAttachment from "./OlogAttachment";
import { v4 as uuidv4 } from 'uuid';
import Attachment from "components/Attachment/Attachment";
import TextInput from "components/shared/input/TextInput";
import { ExternalLink } from "components/shared/ExternalLink";
import { ExternalLink } from "components/shared/Link";
import { FaMarkdown } from "react-icons/fa";
import EmbedImageDialog from "./EmbedImageDialog";
import HtmlPreviewModal from "./HtmlPreviewModal";
Expand Down
2 changes: 1 addition & 1 deletion src/components/log/LogContainer.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect } from "react";
import { ologApi } from "api/ologApi";
import { ServerErrorPage } from "components/ErrorPage";
import { ServerErrorPage } from "components/shared/error";
import { LinearProgress } from "@mui/material";
import { useDispatch } from "react-redux";
import { updateCurrentLogEntry } from "features/currentLogEntryReducer";
Expand Down
2 changes: 1 addition & 1 deletion src/components/log/LogHistory/LogHistoryContainer.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";
import { ologApi } from "api/ologApi";
import { ServerErrorPage } from "components/ErrorPage";
import { ServerErrorPage } from "components/shared/error";
import { LinearProgress } from "@mui/material";
import LogHistory from "./LogHistory";

Expand Down
62 changes: 0 additions & 62 deletions src/components/shared/ExternalLink.js

This file was deleted.

21 changes: 0 additions & 21 deletions src/components/shared/InternalLink.js

This file was deleted.

3 changes: 3 additions & 0 deletions src/components/shared/Link/InternalLink.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ export const InternalButtonLink = ({ children, label, to, ...props }) => {
component={RouterLink}
to={to}
aria-label={label ? label : children}
sx={{
whiteSpace: "nowrap"
}}
{...props}
>
{children}
Expand Down
88 changes: 88 additions & 0 deletions src/components/shared/error/ErrorBoundary.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { Button, styled } from "@mui/material";
import ErrorPage from "./ErrorPage";
import { useCallback } from "react";
import { useEffectOnMount } from "hooks/useMountEffects";
import { ErrorBoundary, useErrorBoundary } from "react-error-boundary";
import theme from "config/theme";

function ReturnHomeButton({ resetErrorBoundary }) {
return (
<Button
variant="contained"
onClick={resetErrorBoundary}
aria-label="Reload Page and navigate home"
>
Return to Home
</Button>
);
}

/**
* ErrorBoundary calls resetErrorBoundary prop of this component
* @param {string} error error message
* @param {string} supportHref app support link
* @param {string} resetErrorBoundary callback for reseting error boundary
* @param {string} error error object raised by the application
* @returns
*/
function AwSnap({ error, supportHref, resetErrorBoundary }) {
console.error("AwSnap", error);

return (
<ErrorPage
title="Whoops! Something went wrong"
titleProps={{ component: "h1", variant: "h2" }}
details={error.stack}
supportHref={supportHref}
// Navigation etc features don't work after error boundary has been breached
// So we must override the secondary action with a button that resets the boundary
SecondaryActionComponent={
<ReturnHomeButton resetErrorBoundary={resetErrorBoundary} />
}
/>
);
}

function WindowErrorRedirect({ children }) {
const { showBoundary } = useErrorBoundary();
const onError = useCallback(
(event) => {
showBoundary(event.error ?? event.reason);
},
[showBoundary]
);

useEffectOnMount(() => {
window.addEventListener("error", onError);
window.addEventListener("unhandledrejection", onError);
return function cleanup() {
window.removeEventListener("error", onError);
window.removeEventListener("unhandledrejection", onError);
};
});

return children;
}

export const AppErrorBoundary = styled(({ children, supportHref }) => {

const onReset=() => {
window.location.href = "/";
};

return (
<ErrorBoundary
FallbackComponent={({resetErrorBoundary, error }) => (
<AwSnap
supportHref={supportHref}
resetErrorBoundary={resetErrorBoundary}
error={error}
/>
)}
onReset={onReset}
>
<WindowErrorRedirect>{children}</WindowErrorRedirect>
</ErrorBoundary>
);
})(theme);

Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import {
import { element, object, string } from "prop-types";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import theme from "config/theme";
import { ExternalButtonLink } from "components/shared/ExternalLink";
import { InternalButtonLink } from "components/shared/InternalLink";
import { ExternalButtonLink, InternalButtonLink } from "components/shared/Link";

const propTypes = {
/** String describing the most important informatio about the error */
Expand All @@ -37,6 +36,7 @@ const ErrorPage = styled(({
details,
SecondaryActionComponent,
supportHref,
homeHref="/",
titleProps,
subtitleProps,
className
Expand Down Expand Up @@ -107,7 +107,7 @@ const ErrorPage = styled(({
{SecondaryActionComponent ?? (
<InternalButtonLink
variant="contained"
to="/"
to={homeHref}
>
Return to Home
</InternalButtonLink>
Expand Down
20 changes: 20 additions & 0 deletions src/components/shared/error/ServerError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ErrorPage } from "./ErrorPage";

export const ServerErrorPage = ({ message, status, supportHref }) => {
// Define some fallback messages if none provided
if (!message) {
if (status?.toString().startsWith("5")) {
message = "Whoops, looks like you broke the internet!";
} else {
message = "Page not found";
}
}

return (
<ErrorPage
title={message}
subtitle={status}
supportHref={supportHref}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ const propTypes = {
supportHref: string
};

const ServerErrorPage = styled(({ message, status, supportHref, className }) => {
const ServerErrorPage = styled(({ message, status, supportHref, homeHref, className }) => {
// Define some fallback messages if none provided
if (!message) {
if (status?.toString().startsWith("5")) {
message = "Whoops, looks like you broke the internet 😬";
message = "Whoops, looks like you broke the internet!";
} else {
message = "Page not found";
}
Expand All @@ -27,6 +27,7 @@ const ServerErrorPage = styled(({ message, status, supportHref, className }) =>
title={message}
subtitle={status}
supportHref={supportHref}
homeHref={homeHref}
className={className}
/>
);
Expand Down
File renamed without changes.
23 changes: 23 additions & 0 deletions src/hooks/useMountEffects.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useEffect, useRef } from "react";

function useMountEffect(effect, deps = [], afterMount) {
const didMountRef = useRef(false);

useEffect(() => {
let cleanup;
if (didMountRef.current === afterMount) {
cleanup = effect();
}
didMountRef.current = true;
return cleanup;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps);
}

export function useEffectOnMount(effect) {
useMountEffect(effect, [], false);
}

export function useEffectAfterMount(effect, deps) {
useMountEffect(effect, deps, true);
}
Loading

0 comments on commit 4a16587

Please sign in to comment.