Skip to content

Commit

Permalink
Merge pull request #41 from NASA-IMPACT/feature/uhoh
Browse files Browse the repository at this point in the history
Restructure error pages
  • Loading branch information
danielfdsilva authored Mar 3, 2022
2 parents e8e1606 + f2f0350 commit 8231473
Show file tree
Hide file tree
Showing 13 changed files with 172 additions and 72 deletions.
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
APP_TITLE=Dashboard Delta
APP_DESCRIPTION=Earth changing dashboard
APP_CONTACT_EMAIL=[email protected]

# If the app is being served in from a subfolder, the domain url must be set.
# For example, if the app is served from /mysite:
Expand Down
2 changes: 1 addition & 1 deletion app/scripts/components/about/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function About() {
const thematic = useThematicArea();
const pageMdx = useMdxPageLoader(thematic?.content);

if (!thematic) return resourceNotFound();
if (!thematic) throw resourceNotFound();

return (
<PageMainContent>
Expand Down
2 changes: 1 addition & 1 deletion app/scripts/components/datasets/hub/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { useThematicArea } from '../../../utils/thematics';

function DatasetsHub() {
const thematic = useThematicArea();
if (!thematic) return resourceNotFound();
if (!thematic) throw resourceNotFound();

return (
<PageMainContent>
Expand Down
2 changes: 1 addition & 1 deletion app/scripts/components/datasets/s-explore/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ function DatasetsExplore() {
setPanelRevealed(!isMediumDown);
}, [isMediumDown]);

if (!thematic || !dataset) return resourceNotFound();
if (!thematic || !dataset) throw resourceNotFound();

return (
<>
Expand Down
2 changes: 1 addition & 1 deletion app/scripts/components/datasets/s-overview/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function DatasetsOverview() {
const dataset = useThematicAreaDataset();
const pageMdx = useMdxPageLoader(dataset?.content);

if (!thematic || !dataset) return resourceNotFound();
if (!thematic || !dataset) throw resourceNotFound();

return (
<>
Expand Down
2 changes: 1 addition & 1 deletion app/scripts/components/datasets/s-usage/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ function DatasetsUsage() {
const thematic = useThematicArea();
const dataset = useThematicAreaDataset();

if (!thematic || !dataset) return resourceNotFound();
if (!thematic || !dataset) throw resourceNotFound();

return (
<>
Expand Down
2 changes: 1 addition & 1 deletion app/scripts/components/discoveries/hub/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { useThematicArea } from '../../../utils/thematics';

function DiscoveriesHub() {
const thematic = useThematicArea();
if (!thematic) return resourceNotFound();
if (!thematic) throw resourceNotFound();

return (
<PageMainContent>
Expand Down
2 changes: 1 addition & 1 deletion app/scripts/components/discoveries/single/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function DiscoveriesSingle() {
const discovery = useThematicAreaDiscovery();
const pageMdx = useMdxPageLoader(discovery?.content);

if (!thematic || !discovery) return resourceNotFound();
if (!thematic || !discovery) throw resourceNotFound();

const { media } = discovery.data;

Expand Down
2 changes: 1 addition & 1 deletion app/scripts/components/home/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const IntroFoldActions = styled.div`

function Home() {
const thematic = useThematicArea();
if (!thematic) return resourceNotFound();
if (!thematic) throw resourceNotFound();

return (
<PageMainContent>
Expand Down
128 changes: 95 additions & 33 deletions app/scripts/components/uhoh/fatal-error.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import React from 'react';
import UhOh from '.';
import T from 'prop-types';
import { useLocation } from 'react-router-dom';

import LayoutRoot from '../common/layout-root';
import UhOh from '.';
import LayoutRoot, {
LayoutProps,
LayoutRootContextProvider
} from '$components/common/layout-root';
import PageHero from '$components/common/page-hero';
import { FoldProse } from '$components/common/fold';
import { PageMainContent } from '$styles/page';

import { makeAbsUrl } from '../../utils/history';
import { makeAbsUrl } from '$utils/history';
import { useEffectPrevious } from '$utils/use-effect-previous';

export default class ErrorBoundary extends React.Component {
static getDerivedStateFromError(error) {
Expand All @@ -13,43 +22,96 @@ export default class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { error: null };
this.clearError = this.clearError.bind(this);
}

clearError() {
this.setState({ error: null });
}

render() {
const { error } = this.state;
return (
<Child
error={this.state.error}
clearError={this.clearError}
{...this.props}
/>
);
}
}

// Child component only needed to use hooks.
function Child(props) {
const { children, error, clearError } = props;
const { pathname } = useLocation();

// eslint-disable-next-line react/prop-types
if (!error) return this.props.children;
// If the pathname changed (because the user navigated) clear the error so the
// page renders again.
useEffectPrevious(
(_, mounted) => {
if (mounted) clearError();
},
[pathname]
);

if (error.resNotFound)
return (
<LayoutRoot pageTitle='Server error'>
// eslint-disable-next-line react/prop-types
if (!error) return children;

// Note (this is a chicken and egg problem)
// The error boundary wraps all the contexts (via Composer in main.js) to be
// able to capture errors that happen in them. When an error occurs those
// contexts are not rendered and so here we have to add the contexts needed by
// the elements.

if (error.resNotFound)
return (
<LayoutRootContextProvider>
<LayoutRoot>
<UhOh />
</LayoutRoot>
);
</LayoutRootContextProvider>
);

return (
<LayoutRoot pageTitle='Server error'>
<p>UhOh</p>
<p>That&apos;s a fatal error</p>
<p>
Something went wrong and we were not able to fulfill your request.
This is on our side and we&apos;ll fix it!
</p>
<p>
In the meantime you can try again by refreshing the page or visit the{' '}
<a href={makeAbsUrl('/')} title='Visit homepage'>
homepage
</a>
.
</p>
<p>
If this error keeps on happening you can reach us via{' '}
<a href='mailto:mail' title='Send us an email'>
email
</a>
</p>
return (
<LayoutRootContextProvider>
<LayoutRoot>
<LayoutProps title='Critical Error' />
<PageMainContent>
<PageHero
title='Critical Error'
description="That's a fatal error."
/>
<FoldProse>
<p>
Something went wrong and we were not able to fulfill your request.
This is on our side and we&apos;ll fix it!
</p>
<p>
In the meantime you can try again by refreshing the page or visit
the{' '}
<a href={makeAbsUrl('/')} title='Visit homepage'>
homepage
</a>
.
</p>
<p>
If this error keeps on happening you can reach us via{' '}
<a
href={`mailto:${process.env.APP_CONTACT_EMAIL}`}
title='Send us an email'
>
{process.env.APP_CONTACT_EMAIL}
</a>
</p>
</FoldProse>
</PageMainContent>
</LayoutRoot>
);
}
</LayoutRootContextProvider>
);
}

Child.propTypes = {
children: T.node,
error: T.object,
clearError: T.func
};
40 changes: 26 additions & 14 deletions app/scripts/components/uhoh/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import React from 'react';

import { FoldProse } from '$components/common/fold';
import PageHero from '$components/common/page-hero';
import { LayoutProps } from '$components/common/layout-root';
import { PageMainContent } from '$styles/page';

export const resourceNotFound = () => {
const e = new Error('Resource not found');
e.resNotFound = true;
Expand All @@ -8,20 +13,27 @@ export const resourceNotFound = () => {

function UhOh() {
return (
<div>
<p>UhOh</p>
<p>That&apos;s a 404 error</p>
<p>
We were not able to find the page you are looking for. It may have been
archived or removed.
</p>
<p>
If you think this page should be here let us know via{' '}
<a href='mailto:mail' title='Send us an email'>
email
</a>
</p>
</div>
<>
<LayoutProps title='Not found' />
<PageMainContent>
<PageHero title='Page not found' description="That's a 404 error." />
<FoldProse>
<p>
We were not able to find the page you are looking for. It may have
been archived or removed.
</p>
<p>
If you think this page should be here let us know via{' '}
<a
href={`mailto:${process.env.APP_CONTACT_EMAIL}`}
title='Send us an email'
>
{process.env.APP_CONTACT_EMAIL}
</a>
</p>
</FoldProse>
</PageMainContent>
</>
);
}

Expand Down
34 changes: 17 additions & 17 deletions app/scripts/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,29 @@ import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { DevseedUiThemeProvider } from '@devseed-ui/theme-provider';
import deltaThematics from 'delta/thematics';

import theme, { GlobalStyles } from './styles/theme';
import history from './utils/history';
import theme, { GlobalStyles } from '$styles/theme';
import history from '$utils/history';
import LayoutRoot, {
LayoutRootContextProvider
} from './components/common/layout-root';
} from '$components/common/layout-root';

// Views
import UhOh from './components/uhoh';
import ErrorBoundary from './components/uhoh/fatal-error';
const RootHome = lazy(() => import('./components/root-home'));
const RootAbout = lazy(() => import('./components/root-about'));
import UhOh from '$components/uhoh';
import ErrorBoundary from '$components/uhoh/fatal-error';
const RootHome = lazy(() => import('$components/root-home'));
const RootAbout = lazy(() => import('$components/root-about'));

const Home = lazy(() => import('./components/home'));
const About = lazy(() => import('./components/about'));
const DiscoveriesHub = lazy(() => import('./components/discoveries/hub'));
const DiscoveriesSingle = lazy(() => import('./components/discoveries/single'));
const Home = lazy(() => import('$components/home'));
const About = lazy(() => import('$components/about'));
const DiscoveriesHub = lazy(() => import('$components/discoveries/hub'));
const DiscoveriesSingle = lazy(() => import('$components/discoveries/single'));

const DatasetsHub = lazy(() => import('./components/datasets/hub'));
const DatasetsExplore = lazy(() => import('./components/datasets/s-explore'));
const DatasetsUsage = lazy(() => import('./components/datasets/s-usage'));
const DatasetsOverview = lazy(() => import('./components/datasets/s-overview'));
const DatasetsHub = lazy(() => import('$components/datasets/hub'));
const DatasetsExplore = lazy(() => import('$components/datasets/s-explore'));
const DatasetsUsage = lazy(() => import('$components/datasets/s-usage'));
const DatasetsOverview = lazy(() => import('$components/datasets/s-overview'));

const Sandbox = lazy(() => import('./components/sandbox'));
const Sandbox = lazy(() => import('$components/sandbox'));

// Contexts

Expand Down Expand Up @@ -58,8 +58,8 @@ function Root() {
return (
<BrowserRouter history={history}>
<DevseedUiThemeProvider theme={theme}>
<GlobalStyles />
<Composer components={composingComponents}>
<GlobalStyles />
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path='/' element={<LayoutRoot />}>
Expand Down
25 changes: 25 additions & 0 deletions app/scripts/utils/use-effect-previous.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useEffect, useRef } from 'react';

type EffectPreviousCb<T> = (previous: T, mounted: boolean) => void | (() => void)

/**
* Same behavior as React's useEffect but called with the values for the
* previous dependencies and with a flag tracking whether or not the component
* is mounted
* @param {func} cb Hook callback
* @param {array} deps Hook dependencies.
*/
export function useEffectPrevious<T extends React.DependencyList>(cb: EffectPreviousCb<T> , deps: T) {
const prev = useRef<React.DependencyList>();
const mounted = useRef(false);
const unchangingCb = useRef<EffectPreviousCb<React.DependencyList>>(cb);
unchangingCb.current = cb;

useEffect(() => {
const r = unchangingCb.current(prev.current, mounted.current);
prev.current = deps;
if (!mounted.current) mounted.current = true;
return r;
/* eslint-disable-next-line react-hooks/exhaustive-deps */
}, deps);
}

0 comments on commit 8231473

Please sign in to comment.