From 49bdfc8e43012a69efc1e90e29b4fb38f51c703e Mon Sep 17 00:00:00 2001 From: RayGervais Date: Fri, 3 Apr 2020 00:39:15 -0400 Subject: [PATCH 1/5] adds: 404 page front-end redirect concept adds: optional chaining to url query parsing --- src/frontend/src/pages/404.jsx | 82 ++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 src/frontend/src/pages/404.jsx diff --git a/src/frontend/src/pages/404.jsx b/src/frontend/src/pages/404.jsx new file mode 100644 index 0000000000..b2fedade1b --- /dev/null +++ b/src/frontend/src/pages/404.jsx @@ -0,0 +1,82 @@ +import React from 'react'; +import Header from '../components/Header'; +import Typography from '@material-ui/core/Typography'; +import { ThemeProvider, makeStyles } from '@material-ui/core/styles'; + +const useStyles = makeStyles((theme) => ({ + h1: { + position: 'absolute', + color: 'white', + fontFamily: 'Roboto', + fontWeight: 'bold', + opacity: 0.85, + fontSize: '12rem', + display: 'block', + top: theme.spacing(20), + left: theme.spacing(8), + [theme.breakpoints.between('xs', 'sm')]: { + fontSize: '4rem', + textAlign: 'left', + left: theme.spacing(4), + right: theme.spacing(4), + top: theme.spacing(14), + }, + [theme.breakpoints.between('md', 'lg')]: { + fontSize: '8rem', + }, + [theme.breakpoints.up('xl')]: { + fontSize: '12rem', + }, + }, + h2: { + position: 'absolute', + color: 'white', + fontFamily: 'Roboto', + fontSize: '2rem', + display: 'block', + bottom: theme.spacing(12), + left: theme.spacing(8), + lineHeight: 'inherit', + letterSpacing: 'inherit', + transition: 'all linear 1s', + [theme.breakpoints.between('xs', 'sm')]: { + textAlign: 'left', + fontSize: '2rem', + left: theme.spacing(4), + right: theme.spacing(4), + }, + [theme.breakpoints.between('md', 'lg')]: { + fontSize: '4rem', + }, + [theme.breakpoints.up('xl')]: { + fontSize: '8rem', + }, + }, +})); + +const ErrorPage = (props) => { + const classes = useStyles(); + const params = new URLSearchParams(props.location.search); + let originalUrl = params + .get('search') + ?.replace('/', '') + ?.match(/([A-Z]?[^A-Z]*)/g) + ?.slice(0, -1) + ?.join(' '); + + return ( +
+
+ + + Sorry! + + + Could Not Find:
{originalUrl}
+
+
+
+ ); +}; + +export default ErrorPage; From f32a8cdc4783feb5ef745c5edc1f6836e95baf94 Mon Sep 17 00:00:00 2001 From: raygervais Date: Sun, 5 Apr 2020 14:41:39 -0400 Subject: [PATCH 2/5] fixes: node semantics and component structure for upcoming wrapper --- src/backend/web/app.js | 12 ++++++++++++ src/frontend/src/pages/404.jsx | 9 +-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/backend/web/app.js b/src/backend/web/app.js index 88529f994f..56ac9eaf20 100644 --- a/src/backend/web/app.js +++ b/src/backend/web/app.js @@ -67,4 +67,16 @@ app.use(logger); // Include our router with all endpoints app.use('/', router); +/** + * 404 Handler, Pass to front-end + * Leverage .status because adding the `404` status in redirect causes "Not Found. Redirecting to /404?search=" to display. + */ +app.use((req, res, _) => { + const originalUrl = encodeURIComponent(req.originalUrl.trim()); + const redirectUrl = `/404?search=${originalUrl}`; + res + .status(404) + .redirect(redirectUrl) +}); + module.exports = app; diff --git a/src/frontend/src/pages/404.jsx b/src/frontend/src/pages/404.jsx index b2fedade1b..2c0d055e5e 100644 --- a/src/frontend/src/pages/404.jsx +++ b/src/frontend/src/pages/404.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import Header from '../components/Header'; import Typography from '@material-ui/core/Typography'; import { ThemeProvider, makeStyles } from '@material-ui/core/styles'; @@ -57,16 +56,10 @@ const useStyles = makeStyles((theme) => ({ const ErrorPage = (props) => { const classes = useStyles(); const params = new URLSearchParams(props.location.search); - let originalUrl = params - .get('search') - ?.replace('/', '') - ?.match(/([A-Z]?[^A-Z]*)/g) - ?.slice(0, -1) - ?.join(' '); + let originalUrl = decodeURIComponent(params.get('search')).replace('/', ''); return (
-
Sorry! From d594e2bd17197041c9610c8f991b553c431bdd37 Mon Sep 17 00:00:00 2001 From: raygervais Date: Sun, 5 Apr 2020 18:38:43 -0400 Subject: [PATCH 3/5] adds: 404 concept styling adds: styling improvements --- src/backend/web/app.js | 8 +- src/frontend/src/pages/404.jsx | 167 +++++++++++++++++++++++++++------ 2 files changed, 140 insertions(+), 35 deletions(-) diff --git a/src/backend/web/app.js b/src/backend/web/app.js index 56ac9eaf20..ed685932b7 100644 --- a/src/backend/web/app.js +++ b/src/backend/web/app.js @@ -71,12 +71,8 @@ app.use('/', router); * 404 Handler, Pass to front-end * Leverage .status because adding the `404` status in redirect causes "Not Found. Redirecting to /404?search=" to display. */ -app.use((req, res, _) => { - const originalUrl = encodeURIComponent(req.originalUrl.trim()); - const redirectUrl = `/404?search=${originalUrl}`; - res - .status(404) - .redirect(redirectUrl) +app.use((req, res) => { + res.status(404).redirect(`/404`); }); module.exports = app; diff --git a/src/frontend/src/pages/404.jsx b/src/frontend/src/pages/404.jsx index 2c0d055e5e..7a9137a131 100644 --- a/src/frontend/src/pages/404.jsx +++ b/src/frontend/src/pages/404.jsx @@ -1,24 +1,36 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; +import PageBase from './PageBase'; import Typography from '@material-ui/core/Typography'; import { ThemeProvider, makeStyles } from '@material-ui/core/styles'; +import { Fab } from '@material-ui/core'; +import Grid from '@material-ui/core/Grid'; +import ArrowBack from '@material-ui/icons/ArrowBack'; +import Card from '@material-ui/core/Card'; +import CardActions from '@material-ui/core/CardActions'; +import CardContent from '@material-ui/core/CardContent'; const useStyles = makeStyles((theme) => ({ + root: { + zIndex: 100, + padding: theme.spacing(2, 4, 2, 4), + position: 'relative', + top: '40vh', + margin: 'auto', + backgroundColor: theme.palette.primary.main, + overflow: 'visible', + [theme.breakpoints.between('xs', 'sm')]: { + top: theme.spacing(24), + }, + }, h1: { - position: 'absolute', - color: 'white', fontFamily: 'Roboto', fontWeight: 'bold', opacity: 0.85, + color: theme.palette.grey[100], fontSize: '12rem', display: 'block', - top: theme.spacing(20), - left: theme.spacing(8), [theme.breakpoints.between('xs', 'sm')]: { fontSize: '4rem', - textAlign: 'left', - left: theme.spacing(4), - right: theme.spacing(4), - top: theme.spacing(14), }, [theme.breakpoints.between('md', 'lg')]: { fontSize: '8rem', @@ -28,21 +40,16 @@ const useStyles = makeStyles((theme) => ({ }, }, h2: { - position: 'absolute', - color: 'white', fontFamily: 'Roboto', fontSize: '2rem', display: 'block', - bottom: theme.spacing(12), - left: theme.spacing(8), + color: theme.palette.grey[200], + marginTop: '1.75rem', lineHeight: 'inherit', letterSpacing: 'inherit', - transition: 'all linear 1s', + transition: 'all linear 350ms', [theme.breakpoints.between('xs', 'sm')]: { - textAlign: 'left', fontSize: '2rem', - left: theme.spacing(4), - right: theme.spacing(4), }, [theme.breakpoints.between('md', 'lg')]: { fontSize: '4rem', @@ -51,24 +58,126 @@ const useStyles = makeStyles((theme) => ({ fontSize: '8rem', }, }, + link: { + color: 'white', + fontFamily: 'Roboto, sans-serif', + textDecoration: 'none', + fontSize: '1.5rem', + margin: '0 0.5rem 0 0.5rem', + }, + errorImg: { + backgroundPosition: 'center', + backgroundRepeat: 'no-repeat', + backgroundAttachment: 'scroll', + backgroundSize: 'cover', + height: 'calc(100vh - 6em)', + transition: 'opacity 1s ease-in-out', + position: 'absolute', + height: '100vh', + width: '100vw', + top: 0, + }, + fab: { + position: 'relative', + fontSize: '1.5rem', + color: 'white', + bottom: -45, + zIndex: 200, + backgroundColor: '#57395e', + transition: 'all linear 250ms', + '&:hover': { + backgroundColor: '#44234c', + }, + [theme.breakpoints.between('xs', 'sm')]: { + right: '-2rem', + }, + }, + + buttonText: { + fontSize: '1.5rem', + paddingLeft: theme.spacing(1), + [theme.breakpoints.between('xs', 'sm')]: { + display: 'none', + }, + }, })); +function RetrieveBackgroundAsset() { + const [backgroundImgSrc, setBackgroundImgSrc] = useState(''); + const [transitionBackground, setTransitionBackground] = useState(true); + const classes = useStyles(); + + useEffect(() => { + async function getBackgroundImgSrc() { + // Uses https://unsplash.com/collections/1538150/milkyway collection + /* Other Options: + - https://unsplash.com/collections/291422/night-lights + */ + + // Ensure we are using an image which fits correctly to user's viewspace + const dimensions = `${window.innerWidth}x${window.innerHeight}`; + const response = await fetch(`https://source.unsplash.com/collection/1538150/${dimensions}`); + + if (response.status !== 200) { + setBackgroundImgSrc('../../images/hero-banner.png'); + throw new Error(response.statusText); + } + + setBackgroundImgSrc(response.url); + setTransitionBackground(false); + } + + getBackgroundImgSrc(); + }, []); + + return ( +
+ ); +} + const ErrorPage = (props) => { const classes = useStyles(); - const params = new URLSearchParams(props.location.search); - let originalUrl = decodeURIComponent(params.get('search')).replace('/', ''); return ( -
- - - Sorry! - - - Could Not Find:
{originalUrl}
-
-
-
+ + + + + + + + 404 + + + We Could Not Find What You Were Looking For! + + + + + + + Let's Go Back + + + + + + + + + ); }; From b427130b015ba631cb2a5048fce09451f79299bf Mon Sep 17 00:00:00 2001 From: raygervais Date: Tue, 7 Apr 2020 23:31:10 -0400 Subject: [PATCH 4/5] refactors: background loading concept to dedicated component fixes: rebase and component cleanup reverts: colors back to secondary base adds: authenticaton 403 concept and 500 wip --- src/backend/web/app.js | 14 +- src/backend/web/authentication.js | 16 +- src/frontend/src/components/Banner/Banner.jsx | 125 +++---------- .../components/DynamicBackgroundContainer.jsx | 70 +++++++ src/frontend/src/pages/404.jsx | 60 +----- src/frontend/src/pages/error.jsx | 171 ++++++++++++++++++ 6 files changed, 296 insertions(+), 160 deletions(-) create mode 100644 src/frontend/src/components/DynamicBackgroundContainer.jsx create mode 100644 src/frontend/src/pages/error.jsx diff --git a/src/backend/web/app.js b/src/backend/web/app.js index ed685932b7..17eef6863e 100644 --- a/src/backend/web/app.js +++ b/src/backend/web/app.js @@ -67,12 +67,24 @@ app.use(logger); // Include our router with all endpoints app.use('/', router); +/** + * Error Handler, Pass to front-end + */ +app.use((err, req, res, next) => { + logger.logger.error(err); + const status = err.status || 500; + res + .status(status) + .redirect(`/error?status=${status}${err.message ? `&message=${encodeURI(err.message)}` : ``}`); +}); + /** * 404 Handler, Pass to front-end * Leverage .status because adding the `404` status in redirect causes "Not Found. Redirecting to /404?search=" to display. */ app.use((req, res) => { - res.status(404).redirect(`/404`); + logger.logger.warn(req.url); + res.status(404).redirect(`/error?status=404`); }); module.exports = app; diff --git a/src/backend/web/authentication.js b/src/backend/web/authentication.js index 1e453c3c7e..0c63efc21c 100644 --- a/src/backend/web/authentication.js +++ b/src/backend/web/authentication.js @@ -132,16 +132,16 @@ function protectWithRedirect(req, res, next) { passport.authenticate('saml')(req, res, next); } +function throwCustomError(message, status) { + let err = Error(message); + err.status = status; + + throw err; +} + // If user is not authenticated, return an appropriate 400 error type function forbidden(req, res) { - if (req.accepts('json')) { - res.status(403).json({ - message: 'Forbidden', - }); - } else { - // TODO: https://github.com/Seneca-CDOT/telescope/issues/890 - res.status(403).send('Forbidden'); - } + throwCustomError('', 403); } // If we aren't redirecting, we're going to forbid this request diff --git a/src/frontend/src/components/Banner/Banner.jsx b/src/frontend/src/components/Banner/Banner.jsx index ed38f2801e..76b70c8055 100644 --- a/src/frontend/src/components/Banner/Banner.jsx +++ b/src/frontend/src/components/Banner/Banner.jsx @@ -6,6 +6,7 @@ import Fab from '@material-ui/core/Fab'; import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown'; import CssBaseline from '@material-ui/core/CssBaseline'; import useSiteMetadata from '../../hooks/use-site-metadata'; +import DynamicBackgroundContainer from '../DynamicBackgroundContainer'; const useStyles = makeStyles((theme) => ({ h1: { @@ -32,18 +33,6 @@ const useStyles = makeStyles((theme) => ({ fontSize: '12rem', }, }, - bannerImg: { - backgroundPosition: 'center', - backgroundRepeat: 'no-repeat', - backgroundAttachment: 'scroll', - backgroundSize: 'cover', - height: '100vh', - opacity: 0.4, - '-webkit-transition': 'opacity 1s ease-in-out', - '-moz-transition': 'opacity 1s ease-in-out', - '-o-transition': 'opacity 1s ease-in-out', - transition: 'opacity 1s ease-in-out', - }, heroBanner: { height: '100vh', }, @@ -101,6 +90,7 @@ const useStyles = makeStyles((theme) => ({ position: 'relative', left: '49.5%', bottom: theme.spacing(20), + zIndex: 300, [theme.breakpoints.between('xs', 'sm')]: { right: theme.spacing(4), left: '80%', @@ -127,74 +117,28 @@ function ScrollDown(props) { ); } -async function getDynamicAsset(url, success, failure) { - try { - const response = await fetch(url); - - if (response.status !== 200) { - throw new Error(response.statusText); - } - - if (response.headers.get('content-type').includes('application/json')) { - success(await response.json()); - return; - } - - success(response); - } catch (error) { - console.error('Error getting dynamic asset', error); - - if (failure) { - failure(); - } - } -} - function RetrieveBannerDynamicAssets() { - const [backgroundImgSrc, setBackgroundImgSrc] = useState(''); - const [transitionBackground, setTransitionBackground] = useState(true); const [stats, setStats] = useState({ stats: { posts: 0, authors: 0, words: 0 } }); const classes = useStyles(); const { telescopeUrl } = useSiteMetadata(); useEffect(() => { - async function getBackgroundImgSrc() { - // Uses https://unsplash.com/collections/894/earth-%26-planets collection - /* Other Options: - - https://unsplash.com/collections/2411320/trend%3A-extreme-neon - - https://unsplash.com/collections/1538150/milkyway - - https://unsplash.com/collections/291422/night-lights - */ - - // Ensure we are using an image which fits correctly to user's viewspace - const dimensions = `${window.innerWidth}x${window.innerHeight}`; + async function getStats() { + const response = await fetch(`${telescopeUrl}/stats/year`); - async function getStats() { - await getDynamicAsset(`${telescopeUrl}/stats/year`, (response) => { - const localeStats = { - posts: response.posts.toLocaleString(), - authors: response.authors.toLocaleString(), - words: response.words.toLocaleString(), - }; - setStats(localeStats); - - // Ease in Background - setTransitionBackground(false); - }); + if (response.status !== 200) { + throw new Error(response.statusText); } - await getDynamicAsset( - `https://source.unsplash.com/collection/894/${dimensions}/`, - (response) => { - setBackgroundImgSrc(response.url); - getStats(); - }, - () => { - // Fallback to default image - setBackgroundImgSrc('../../images/hero-banner.png'); - } - ); + const data = await response.json(); + + const localeStats = { + posts: data.posts.toLocaleString(), + authors: data.authors.toLocaleString(), + words: data.words.toLocaleString(), + }; + setStats(localeStats); } getBackgroundImgSrc(); @@ -202,25 +146,14 @@ function RetrieveBannerDynamicAssets() { return (
-
- - This year {stats.authors} of us have written {stats.words} words and counting. Add yours! - + + {stats.authors !== 0 && ( + + This year {stats.authors} of us have written {stats.words} words and counting. Add + yours! + + )} +
); } @@ -271,13 +204,13 @@ export default function Banner() { > v {gitInfo.version} -
- - - - - -
+
+
+ + + + +
); diff --git a/src/frontend/src/components/DynamicBackgroundContainer.jsx b/src/frontend/src/components/DynamicBackgroundContainer.jsx new file mode 100644 index 0000000000..cefadb05ca --- /dev/null +++ b/src/frontend/src/components/DynamicBackgroundContainer.jsx @@ -0,0 +1,70 @@ +import React, { useEffect, useState } from 'react'; +import { makeStyles } from '@material-ui/core/styles'; + +const useStyles = makeStyles((theme) => ({ + background: { + backgroundPosition: 'center', + backgroundRepeat: 'no-repeat', + backgroundAttachment: 'scroll', + backgroundSize: 'cover', + transition: 'opacity 1s ease-in-out', + position: 'absolute', + height: '100vh', + width: '100%', + top: 0, + }, + child: { + transition: 'opacity 1s ease-in-out', + }, +})); + +export default function DynamicBackgroundContainer(props) { + const [backgroundImgSrc, setBackgroundImgSrc] = useState(''); + const [transitionBackground, setTransitionBackground] = useState(true); + const classes = useStyles(); + + useEffect(() => { + async function getBackgroundImgSrc() { + // Uses https://unsplash.com/collections/1538150/milkyway collection + /* Other Options: + - https://unsplash.com/collections/291422/night-lights + */ + + // Ensure we are using an image which fits correctly to user's viewspace + const dimensions = `${window.innerWidth}x${window.innerHeight}`; + console.log(dimensions); + const response = await fetch(`https://source.unsplash.com/collection/894/${dimensions}`); + + if (response.status !== 200) { + setBackgroundImgSrc('../../images/hero-banner.png'); + setTransitionBackground(false); + throw new Error(response.statusText); + } + + setBackgroundImgSrc(response.url); + setTransitionBackground(false); + } + + getBackgroundImgSrc(); + }, []); + + return ( +
+
+
+ {props.children} +
+
+ ); +} diff --git a/src/frontend/src/pages/404.jsx b/src/frontend/src/pages/404.jsx index 7a9137a131..27bb8a55ec 100644 --- a/src/frontend/src/pages/404.jsx +++ b/src/frontend/src/pages/404.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import PageBase from './PageBase'; import Typography from '@material-ui/core/Typography'; import { ThemeProvider, makeStyles } from '@material-ui/core/styles'; @@ -8,6 +8,7 @@ import ArrowBack from '@material-ui/icons/ArrowBack'; import Card from '@material-ui/core/Card'; import CardActions from '@material-ui/core/CardActions'; import CardContent from '@material-ui/core/CardContent'; +import DynamicBackgroundContainer from '../components/DynamicBackgroundContainer'; const useStyles = makeStyles((theme) => ({ root: { @@ -65,28 +66,16 @@ const useStyles = makeStyles((theme) => ({ fontSize: '1.5rem', margin: '0 0.5rem 0 0.5rem', }, - errorImg: { - backgroundPosition: 'center', - backgroundRepeat: 'no-repeat', - backgroundAttachment: 'scroll', - backgroundSize: 'cover', - height: 'calc(100vh - 6em)', - transition: 'opacity 1s ease-in-out', - position: 'absolute', - height: '100vh', - width: '100vw', - top: 0, - }, fab: { position: 'relative', fontSize: '1.5rem', color: 'white', bottom: -45, zIndex: 200, - backgroundColor: '#57395e', + backgroundColor: theme.palette.secondary.light, transition: 'all linear 250ms', '&:hover': { - backgroundColor: '#44234c', + backgroundColor: theme.palette.secondary.dark, }, [theme.breakpoints.between('xs', 'sm')]: { right: '-2rem', @@ -102,45 +91,6 @@ const useStyles = makeStyles((theme) => ({ }, })); -function RetrieveBackgroundAsset() { - const [backgroundImgSrc, setBackgroundImgSrc] = useState(''); - const [transitionBackground, setTransitionBackground] = useState(true); - const classes = useStyles(); - - useEffect(() => { - async function getBackgroundImgSrc() { - // Uses https://unsplash.com/collections/1538150/milkyway collection - /* Other Options: - - https://unsplash.com/collections/291422/night-lights - */ - - // Ensure we are using an image which fits correctly to user's viewspace - const dimensions = `${window.innerWidth}x${window.innerHeight}`; - const response = await fetch(`https://source.unsplash.com/collection/1538150/${dimensions}`); - - if (response.status !== 200) { - setBackgroundImgSrc('../../images/hero-banner.png'); - throw new Error(response.statusText); - } - - setBackgroundImgSrc(response.url); - setTransitionBackground(false); - } - - getBackgroundImgSrc(); - }, []); - - return ( -
- ); -} - const ErrorPage = (props) => { const classes = useStyles(); @@ -176,7 +126,7 @@ const ErrorPage = (props) => { - + ); }; diff --git a/src/frontend/src/pages/error.jsx b/src/frontend/src/pages/error.jsx new file mode 100644 index 0000000000..9e39055c96 --- /dev/null +++ b/src/frontend/src/pages/error.jsx @@ -0,0 +1,171 @@ +import React from 'react'; +import PageBase from './PageBase'; +import Typography from '@material-ui/core/Typography'; +import { ThemeProvider, makeStyles } from '@material-ui/core/styles'; +import { Fab } from '@material-ui/core'; +import Grid from '@material-ui/core/Grid'; +import ArrowBack from '@material-ui/icons/ArrowBack'; +import Card from '@material-ui/core/Card'; +import CardActions from '@material-ui/core/CardActions'; +import CardContent from '@material-ui/core/CardContent'; +import DynamicBackgroundContainer from '../components/DynamicBackgroundContainer'; +import url from 'url'; + +const useStyles = makeStyles((theme) => ({ + root: { + fontFamily: 'Roboto', + display: 'block', + zIndex: 100, + padding: theme.spacing(2, 4, 2, 4), + position: 'relative', + top: '40vh', + margin: 'auto', + backgroundColor: theme.palette.primary.main, + overflow: 'visible', + [theme.breakpoints.between('xs', 'sm')]: { + top: theme.spacing(24), + }, + }, + h1: { + fontWeight: 'bold', + opacity: 0.85, + color: theme.palette.grey[100], + fontSize: '12rem', + [theme.breakpoints.between('xs', 'sm')]: { + fontSize: '4rem', + }, + [theme.breakpoints.between('md', 'lg')]: { + fontSize: '8rem', + }, + [theme.breakpoints.up('xl')]: { + fontSize: '12rem', + }, + }, + h2: { + fontSize: '2rem', + color: theme.palette.grey[200], + marginTop: '1.75rem', + lineHeight: 'inherit', + letterSpacing: 'inherit', + transition: 'all linear 350ms', + [theme.breakpoints.between('xs', 'sm')]: { + fontSize: '2rem', + }, + [theme.breakpoints.between('md', 'lg')]: { + fontSize: '4rem', + }, + [theme.breakpoints.up('xl')]: { + fontSize: '8rem', + }, + }, + h3: { + fontFamily: 'monospace', + fontSize: '2rem', + padding: theme.spacing(4), + color: theme.palette.grey[300], + // backgroundColor: theme.palette.error.light, + borderRadius: theme.spacing(1), + }, + link: { + color: 'white', + fontFamily: 'Roboto, sans-serif', + textDecoration: 'none', + fontSize: '1.5rem', + margin: '0 0.5rem 0 0.5rem', + }, + fab: { + position: 'relative', + fontSize: '1.5rem', + color: 'white', + bottom: -45, + zIndex: 200, + backgroundColor: theme.palette.secondary.light, + transition: 'all linear 250ms', + '&:hover': { + backgroundColor: theme.palette.secondary.dark, + }, + [theme.breakpoints.between('xs', 'sm')]: { + right: '-2rem', + }, + }, + + buttonText: { + fontSize: '1.5rem', + paddingLeft: theme.spacing(1), + [theme.breakpoints.between('xs', 'sm')]: { + display: 'none', + }, + }, +})); + +function CreateInnerErrorContent(props) { + const classes = useStyles(); + + const errorMessages = { + '400': 'We did not understand the request!', + '401': 'You are not authorized to view this page!', + '403': 'Access is not allowed for the requested page!', + '404': 'We could not find what you were looking for!', + '405': 'Method is not allowed!', + }; + + if (props.status != '500' && !props.message) { + return ( + + {errorMessages[props.status]}{' '} + + ); + } + return ( + + {props.message} + + ); +} + +const ErrorPage = (props) => { + const classes = useStyles(); + const params = new URLSearchParams(document.location.search); + const status = params.get('status'); + const message = params.get('message'); + + console.log(message); + + return ( + + + + + + + + {status} + + + + + + + + + + Let's Go Back + + + + + + + + + + ); +}; + +export default ErrorPage; From 3f98db7932727a33fe67178e5c9af3c9f904e7e7 Mon Sep 17 00:00:00 2001 From: raygervais Date: Sat, 11 Apr 2020 18:49:28 -0400 Subject: [PATCH 5/5] removes: 404.jsx, we'll use error.jsx adds: corrections based on circleCI and SSR requirements --- src/backend/web/app.js | 5 +- src/backend/web/authentication.js | 21 ++- src/frontend/src/components/Banner/Banner.jsx | 2 +- .../components/DynamicBackgroundContainer.jsx | 25 ++-- src/frontend/src/pages/404.jsx | 134 ------------------ src/frontend/src/pages/error.jsx | 91 ++++++------ 6 files changed, 77 insertions(+), 201 deletions(-) delete mode 100644 src/frontend/src/pages/404.jsx diff --git a/src/backend/web/app.js b/src/backend/web/app.js index 17eef6863e..a6ac8b2c47 100644 --- a/src/backend/web/app.js +++ b/src/backend/web/app.js @@ -70,8 +70,9 @@ app.use('/', router); /** * Error Handler, Pass to front-end */ +/* eslint-disable no-unused-vars */ app.use((err, req, res, next) => { - logger.logger.error(err); + logger.logger.error({ error: err }); const status = err.status || 500; res .status(status) @@ -83,7 +84,7 @@ app.use((err, req, res, next) => { * Leverage .status because adding the `404` status in redirect causes "Not Found. Redirecting to /404?search=" to display. */ app.use((req, res) => { - logger.logger.warn(req.url); + logger.logger.warn(`Attempted to access the following unknown URL: ${req.url}`); res.status(404).redirect(`/error?status=404`); }); diff --git a/src/backend/web/authentication.js b/src/backend/web/authentication.js index 0c63efc21c..4fcf8d95e0 100644 --- a/src/backend/web/authentication.js +++ b/src/backend/web/authentication.js @@ -133,20 +133,29 @@ function protectWithRedirect(req, res, next) { } function throwCustomError(message, status) { - let err = Error(message); + const err = Error(message); err.status = status; throw err; } // If user is not authenticated, return an appropriate 400 error type -function forbidden(req, res) { - throwCustomError('', 403); +/* eslint-disable no-unused-vars */ +function forbidden(req, res, next) { + if (req.accepts('json')) { + res.status(403).json({ + message: 'Forbidden', + }); + + return; + } + + throwCustomError('Forbidden: Access is not allowed for the requested page!', 403); } // If we aren't redirecting, we're going to forbid this request -function protectWithoutRedirect(req, res) { - forbidden(req, res); +function protectWithoutRedirect(req, res, next) { + forbidden(req, res, next); } /** @@ -166,7 +175,7 @@ function checkUser(requireAdmin, redirect, req, res, next) { } // Not an admin, so fail this now using best response type else { - forbidden(req, res); + forbidden(req, res, next); } } else { // We don't need an admin, and this is a regular authenticated user, let it pass diff --git a/src/frontend/src/components/Banner/Banner.jsx b/src/frontend/src/components/Banner/Banner.jsx index 76b70c8055..aeaac53ce6 100644 --- a/src/frontend/src/components/Banner/Banner.jsx +++ b/src/frontend/src/components/Banner/Banner.jsx @@ -141,7 +141,7 @@ function RetrieveBannerDynamicAssets() { setStats(localeStats); } - getBackgroundImgSrc(); + getStats(); }, [telescopeUrl]); return ( diff --git a/src/frontend/src/components/DynamicBackgroundContainer.jsx b/src/frontend/src/components/DynamicBackgroundContainer.jsx index cefadb05ca..d847a3ba08 100644 --- a/src/frontend/src/components/DynamicBackgroundContainer.jsx +++ b/src/frontend/src/components/DynamicBackgroundContainer.jsx @@ -25,24 +25,27 @@ export default function DynamicBackgroundContainer(props) { useEffect(() => { async function getBackgroundImgSrc() { - // Uses https://unsplash.com/collections/1538150/milkyway collection - /* Other Options: + try { + // Uses https://unsplash.com/collections/1538150/milkyway collection + /* Other Options: - https://unsplash.com/collections/291422/night-lights */ - // Ensure we are using an image which fits correctly to user's viewspace - const dimensions = `${window.innerWidth}x${window.innerHeight}`; - console.log(dimensions); - const response = await fetch(`https://source.unsplash.com/collection/894/${dimensions}`); + // Ensure we are using an image which fits correctly to user's viewspace + const dimensions = `${window.innerWidth}x${window.innerHeight}`; + const response = await fetch(`https://source.unsplash.com/collection/894/${dimensions}`); - if (response.status !== 200) { + if (response.status !== 200) { + throw new Error(response.statusText); + } + + setBackgroundImgSrc(response.url); + } catch (error) { setBackgroundImgSrc('../../images/hero-banner.png'); + console.error(error); + } finally { setTransitionBackground(false); - throw new Error(response.statusText); } - - setBackgroundImgSrc(response.url); - setTransitionBackground(false); } getBackgroundImgSrc(); diff --git a/src/frontend/src/pages/404.jsx b/src/frontend/src/pages/404.jsx deleted file mode 100644 index 27bb8a55ec..0000000000 --- a/src/frontend/src/pages/404.jsx +++ /dev/null @@ -1,134 +0,0 @@ -import React from 'react'; -import PageBase from './PageBase'; -import Typography from '@material-ui/core/Typography'; -import { ThemeProvider, makeStyles } from '@material-ui/core/styles'; -import { Fab } from '@material-ui/core'; -import Grid from '@material-ui/core/Grid'; -import ArrowBack from '@material-ui/icons/ArrowBack'; -import Card from '@material-ui/core/Card'; -import CardActions from '@material-ui/core/CardActions'; -import CardContent from '@material-ui/core/CardContent'; -import DynamicBackgroundContainer from '../components/DynamicBackgroundContainer'; - -const useStyles = makeStyles((theme) => ({ - root: { - zIndex: 100, - padding: theme.spacing(2, 4, 2, 4), - position: 'relative', - top: '40vh', - margin: 'auto', - backgroundColor: theme.palette.primary.main, - overflow: 'visible', - [theme.breakpoints.between('xs', 'sm')]: { - top: theme.spacing(24), - }, - }, - h1: { - fontFamily: 'Roboto', - fontWeight: 'bold', - opacity: 0.85, - color: theme.palette.grey[100], - fontSize: '12rem', - display: 'block', - [theme.breakpoints.between('xs', 'sm')]: { - fontSize: '4rem', - }, - [theme.breakpoints.between('md', 'lg')]: { - fontSize: '8rem', - }, - [theme.breakpoints.up('xl')]: { - fontSize: '12rem', - }, - }, - h2: { - fontFamily: 'Roboto', - fontSize: '2rem', - display: 'block', - color: theme.palette.grey[200], - marginTop: '1.75rem', - lineHeight: 'inherit', - letterSpacing: 'inherit', - transition: 'all linear 350ms', - [theme.breakpoints.between('xs', 'sm')]: { - fontSize: '2rem', - }, - [theme.breakpoints.between('md', 'lg')]: { - fontSize: '4rem', - }, - [theme.breakpoints.up('xl')]: { - fontSize: '8rem', - }, - }, - link: { - color: 'white', - fontFamily: 'Roboto, sans-serif', - textDecoration: 'none', - fontSize: '1.5rem', - margin: '0 0.5rem 0 0.5rem', - }, - fab: { - position: 'relative', - fontSize: '1.5rem', - color: 'white', - bottom: -45, - zIndex: 200, - backgroundColor: theme.palette.secondary.light, - transition: 'all linear 250ms', - '&:hover': { - backgroundColor: theme.palette.secondary.dark, - }, - [theme.breakpoints.between('xs', 'sm')]: { - right: '-2rem', - }, - }, - - buttonText: { - fontSize: '1.5rem', - paddingLeft: theme.spacing(1), - [theme.breakpoints.between('xs', 'sm')]: { - display: 'none', - }, - }, -})); - -const ErrorPage = (props) => { - const classes = useStyles(); - - return ( - - - - - - - - 404 - - - We Could Not Find What You Were Looking For! - - - - - - - Let's Go Back - - - - - - - - - - ); -}; - -export default ErrorPage; diff --git a/src/frontend/src/pages/error.jsx b/src/frontend/src/pages/error.jsx index 9e39055c96..6ec791cffd 100644 --- a/src/frontend/src/pages/error.jsx +++ b/src/frontend/src/pages/error.jsx @@ -1,15 +1,9 @@ import React from 'react'; +import { Card, CardActions, CardContent, Fab, Grid, Typography } from '@material-ui/core'; import PageBase from './PageBase'; -import Typography from '@material-ui/core/Typography'; -import { ThemeProvider, makeStyles } from '@material-ui/core/styles'; -import { Fab } from '@material-ui/core'; -import Grid from '@material-ui/core/Grid'; +import { makeStyles } from '@material-ui/core/styles'; import ArrowBack from '@material-ui/icons/ArrowBack'; -import Card from '@material-ui/core/Card'; -import CardActions from '@material-ui/core/CardActions'; -import CardContent from '@material-ui/core/CardContent'; -import DynamicBackgroundContainer from '../components/DynamicBackgroundContainer'; -import url from 'url'; +import DynamicBackgroundContainer from '../components/DynamicBackgroundContainer.jsx'; const useStyles = makeStyles((theme) => ({ root: { @@ -63,7 +57,6 @@ const useStyles = makeStyles((theme) => ({ fontSize: '2rem', padding: theme.spacing(4), color: theme.palette.grey[300], - // backgroundColor: theme.palette.error.light, borderRadius: theme.spacing(1), }, link: { @@ -101,18 +94,26 @@ const useStyles = makeStyles((theme) => ({ function CreateInnerErrorContent(props) { const classes = useStyles(); - const errorMessages = { - '400': 'We did not understand the request!', - '401': 'You are not authorized to view this page!', - '403': 'Access is not allowed for the requested page!', - '404': 'We could not find what you were looking for!', - '405': 'Method is not allowed!', - }; + const errorMessages = new Proxy( + { + '400': 'We did not understand the request!', + '401': 'You are not authorized to view this page!', + '403': 'Access is not allowed for the requested page!', + '404': 'We could not find what you were looking for!', + '405': 'Method is not allowed!', + }, + { + get: function (object, property) { + return object.hasOwnProperty(property) ? object[property] : 'Something went wrong!'; + }, + } + ); - if (props.status != '500' && !props.message) { + // If server doesn't send us a custom message, use ones defined above. + if (!props.message) { return ( - {errorMessages[props.status]}{' '} + {errorMessages[props.status]} ); } @@ -123,44 +124,40 @@ function CreateInnerErrorContent(props) { ); } -const ErrorPage = (props) => { +const ErrorPage = ({ location }) => { const classes = useStyles(); - const params = new URLSearchParams(document.location.search); + const params = new URLSearchParams(location.search); const status = params.get('status'); const message = params.get('message'); - console.log(message); - return ( - - - - - {status} - + + + + {status} + - - + + - - - - - Let's Go Back - - - - - + + + + + Let's Go Back + + + +