From d9cd5f8ff9c0cde565518235fc03102257edeb6f Mon Sep 17 00:00:00 2001 From: Matisse Sulzer <160239816+Matisse-Sulzer@users.noreply.github.com> Date: Thu, 18 Apr 2024 16:05:37 +0200 Subject: [PATCH] error pages (#93) * initial draft for error pages * indentation fixed * docs * error pages v1 * indentation fixed * layout adjusted, using RouterProvider instead of BrowserRouter to render errors correctly, tests added * linter * documentation * linter error parameters * Added i18n, changed general structure of App component and got rid of fade animation for the image * linting, package-lock.json fix * package file * changes to paramters in ErrorPage.tsx * use of keyPrefix * keyPrefix * fix --- frontend/.eslintrc.cjs | 1 + frontend/cypress/e2e/ErrorPage.cy.tsx | 16 ++++++ frontend/package-lock.json | 47 ++++++++++++++++-- frontend/package.json | 6 ++- frontend/public/img/error_pigeon.png | Bin 0 -> 2358 bytes frontend/public/{ => img}/logo_ugent.png | Bin frontend/public/locales/en/translation.json | 13 +++-- frontend/public/locales/nl/translation.json | 10 ++++ frontend/src/App.tsx | 10 ++-- frontend/src/Layout.tsx | 15 ++++++ frontend/src/pages/error/ErrorBoundary.tsx | 32 ++++++++++++ frontend/src/pages/error/ErrorPage.tsx | 51 ++++++++++++++++++++ 12 files changed, 186 insertions(+), 15 deletions(-) create mode 100644 frontend/cypress/e2e/ErrorPage.cy.tsx create mode 100644 frontend/public/img/error_pigeon.png rename frontend/public/{ => img}/logo_ugent.png (100%) create mode 100644 frontend/src/Layout.tsx create mode 100644 frontend/src/pages/error/ErrorBoundary.tsx create mode 100644 frontend/src/pages/error/ErrorPage.tsx diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs index 8b34a81b..dd2f81ab 100644 --- a/frontend/.eslintrc.cjs +++ b/frontend/.eslintrc.cjs @@ -40,6 +40,7 @@ module.exports = { "jsdoc/require-param": 0, "jsdoc/require-param-description": 1, "jsdoc/require-param-name": 1, + "jsdoc/require-param-type": 0, "jsdoc/require-property": 1, "jsdoc/require-property-description": 1, "jsdoc/require-property-name": 1, diff --git a/frontend/cypress/e2e/ErrorPage.cy.tsx b/frontend/cypress/e2e/ErrorPage.cy.tsx new file mode 100644 index 00000000..7d998994 --- /dev/null +++ b/frontend/cypress/e2e/ErrorPage.cy.tsx @@ -0,0 +1,16 @@ +describe('Error page test', () => { + it('Error page should load appropriately', () => { + expect( + () => { + cy.request({ + method: 'POST', + path: '**', + body: {name: "fail"}, + failOnStatusCode: false + }).then(response => { + expect(response.status).to.be(404) // is supposed to be 404 + }) + } + ) + }) +}) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7c101d25..4d9a738d 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -30,12 +30,15 @@ "styled-components": "^6.1.8" }, "devDependencies": { + "@types/history": "^4.7.11", "@types/react": "^18.2.55", "@types/react-dom": "^18.2.19", + "@types/react-router-dom": "^5.3.3", + "@types/scheduler": "^0.23.0", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", "@vitejs/plugin-react": "^4.2.1", - "cypress": "^13.6.4", + "cypress": "^13.7.0", "eslint": "^8.56.0", "eslint-plugin-jsdoc": "^48.1.0", "eslint-plugin-react-hooks": "^4.6.0", @@ -45,6 +48,7 @@ "i18next-browser-languagedetector": "^7.2.1", "i18next-http-backend": "^2.5.0", "react-i18next": "^14.1.0", + "scheduler": "^0.23.0", "typescript": "^5.2.2", "vite": "^5.1.7" } @@ -1806,6 +1810,12 @@ "@types/unist": "*" } }, + "node_modules/@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1826,9 +1836,9 @@ "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" }, "node_modules/@types/node": { - "version": "20.12.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.6.tgz", - "integrity": "sha512-3KurE8taB8GCvZBPngVbp0lk5CKi8M9f9k1rsADh0Evdz5SzJ+Q+Hx9uHoFGsLnLnd1xmkDQr2hVhlA0Mn0lKQ==", + "version": "20.12.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", + "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", "dev": true, "optional": true, "dependencies": { @@ -1863,6 +1873,27 @@ "@types/react": "*" } }, + "node_modules/@types/react-router": { + "version": "5.1.20", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", + "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", + "dev": true, + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "node_modules/@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "dev": true, + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, "node_modules/@types/react-transition-group": { "version": "4.4.10", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", @@ -1871,6 +1902,12 @@ "@types/react": "*" } }, + "node_modules/@types/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-YIoDCTH3Af6XM5VuwGG/QL/CJqga1Zm3NkU3HZ4ZHK2fRMPYP1VczsTUqtsf43PH/iJNVlPHAo2oWX7BSdB2Hw==", + "dev": true + }, "node_modules/@types/semver": { "version": "7.5.8", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", @@ -6368,7 +6405,7 @@ "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" diff --git a/frontend/package.json b/frontend/package.json index 89c7e34f..7ea862ec 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -36,10 +36,13 @@ "devDependencies": { "@types/react": "^18.2.55", "@types/react-dom": "^18.2.19", + "@types/react-router-dom": "^5.3.3", + "@types/history": "^4.7.11", + "@types/scheduler": "^0.23.0", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", "@vitejs/plugin-react": "^4.2.1", - "cypress": "^13.6.4", + "cypress": "^13.7.0", "eslint": "^8.56.0", "eslint-plugin-jsdoc": "^48.1.0", "eslint-plugin-react-hooks": "^4.6.0", @@ -49,6 +52,7 @@ "i18next-browser-languagedetector": "^7.2.1", "i18next-http-backend": "^2.5.0", "react-i18next": "^14.1.0", + "scheduler": "^0.23.0", "typescript": "^5.2.2", "vite": "^5.1.7" } diff --git a/frontend/public/img/error_pigeon.png b/frontend/public/img/error_pigeon.png new file mode 100644 index 0000000000000000000000000000000000000000..f2264ead5c834fc6b0e317a2497ae8d11a88d241 GIT binary patch literal 2358 zcmbVO`BxGM7X?AF1@~|%r5yKNa$zLF6c@}S0ds9M%G6ZEB?r?KY;nN~P0LcVlrkkr z)U;g5r7S7OEv&Ts)ZB1yNXI2V=3kh1-hKDHcis>8ymQ_;H}iz2D|ENUZU6uP#kpY# z67K#NvNDod<;%SNWM~@HKR=~9zciPWwNLsBIjSdNdwHbLY~j+~q$Pp<;kcc)NnCx? z0IGMSI5c$e_s|bOT&~W4&D}A9bKt@Lz_F`*m1;l%cnb6e6sKVcnvd>0%ul6{UZMiWVWPfDl{-nZ5_0{1Pn@I`yTA5RiSB8fz?F@ z**tLKJXlL=F}f2r_6eLA)>#Y=l#7(o(x+s;Q`@9BG1frks;i!EP$okj*ancx`8Bzu zUoR!1*Z#N^VO2Q=^Z{VBDc*#VvBcM~(lN-k2U{^-ZrQK2%RrnN7(Wkb1qK%MpvwK5 zuDtMPqr-_^(ewtz&5y>VI^MRf2Q77)UC#Nj zd#s#|0dkbuAZClAVo%W{K8L99rMrhw$O!K*iTBP%W%L0;8q|i}>W;LcW6bh-GVA)Y z_?pT$=&_ExAD6el%eY{3*HwGnaF=~l4XXJp{%KPAHf&y%k>aJFpp1)RYt<+Yv)`~$DdAmy_B^x5xxeB3ifIUBEhFXwt$_5Lw6crELYv@ zKwHTPEwmF9OQ5S3o_mEmuJFX7J4CsB8{ml3#;taw4Mk7Oa#2{wh`O}6sX%Q9Y;vy} zlIf@BlwXCxQ?=2mKgrU|J;1fiX{|Lsl= zHX!x4ZS$`)rk|}L7`QL~Z_ecNXZr)@Lv>6JofErl_t=akk?M5cNR^>sux*HRTN^WXnoq3osW(HC;R8gZk3-lx`3IDP_#&poZw(x}p{ zwj4FKHJJGXQ6KW@iJ5fG{jG~3)*3F49X=|^PrJ3KWlH%0Bi)Lx7*+g~t^usWsN^{; z3HRKbbsd}jV2}gi2h~RU4oSV#pt;WJko-^?yuef5JcmCV1)0WT1u+HWfv(E%ZCY01(|r!Jb-%GO`T zs3_8uR3e-jE)f*5hrT&ejZXH9=6%pRX>!l4IK!nK7OZP|=3QJ~4DfZ#c>lBm7TROj znMK4PHy|O+T^nGu9p`@m$C%}Wo&=^?gM2b~WiT^LXF&%h9vRqjxl7^c(cwfv-8unw zt25aT6OmHp;lqORlZg5ca~eu$Y_fjd^zK-7UP5}_=M`-9ru@SLJ_<=aoCVy)CR4PB z$-Tzvg80~#z02BT6^J_WC3R+5CFPJz4!R&_v!jzes0wUix*I{1v7D2!KDScR&X%Sdwx7_fKtSX%Zm}Rhuc>n3YwQ8AV6~r75W#ui+s(I&{r_LXo zEN}FbdRtQ-ZifnSH?~_mK}+Xr?em)4xOlPpq+KcT#L!{q%^M^3e52sFiErtQHHC=V z4Yl-FuoaX3{e=0ylgStQ22(Y&e@!G|E8 zK(;bBDbD>>H>>8m`m!Hx5N>4dpDhc>sUzl(7tBr!ZzO~>Ag9^PrGbDPQU&tdMI@Ke z44{$SAZPbt7Ih@UD&#rD0ixOBfpMUE$Ib9kab@AAJ1gRu*Nf-72gjhqP? z`-TJl5F#&xlss}v$Qyg{*?Fl)Rmf2l^)livd=UE?RL z?L7rwX%K4aee)3$)=eqZ>3`Y@N97dpoAw^@Z}> + } errorElement={}> } /> }> } /> @@ -27,8 +28,5 @@ const router = createBrowserRouter( * @returns - The main application component */ export default function App(): React.JSX.Element { - return ( - - - ); -} \ No newline at end of file + return ; +} diff --git a/frontend/src/Layout.tsx b/frontend/src/Layout.tsx new file mode 100644 index 00000000..2184d5c7 --- /dev/null +++ b/frontend/src/Layout.tsx @@ -0,0 +1,15 @@ +import { Outlet } from "react-router-dom"; +import { Header } from "./components/Header/Header.tsx"; + +/** + * Basic layout component that will be used on all routes. + * @returns The Layout component + */ +export function Layout(): JSX.Element { + return ( + <> +
+ + + ); +} \ No newline at end of file diff --git a/frontend/src/pages/error/ErrorBoundary.tsx b/frontend/src/pages/error/ErrorBoundary.tsx new file mode 100644 index 00000000..d6d48ca9 --- /dev/null +++ b/frontend/src/pages/error/ErrorBoundary.tsx @@ -0,0 +1,32 @@ +import { useRouteError, isRouteErrorResponse } from "react-router-dom"; +import { ErrorPage } from "./ErrorPage.tsx"; +import { useTranslation } from "react-i18next"; + +/** + * This component will render the ErrorPage component with the appropriate data when an error occurs. + * @returns The ErrorBoundary component + */ +export function ErrorBoundary() { + const error = useRouteError(); + const { t } = useTranslation('translation', { keyPrefix: 'error' }); + + if (isRouteErrorResponse(error)) { + if (error.status == 404) { + return ( + + ); + } else if (error.status == 403) { + return ( + + ); + } else if (error.status >= 400 && error.status <= 499) { + return ( + + ); + } else if (error.status >= 500 && error.status <= 599) { + return ( + + ); + } + } +} diff --git a/frontend/src/pages/error/ErrorPage.tsx b/frontend/src/pages/error/ErrorPage.tsx new file mode 100644 index 00000000..edabd86e --- /dev/null +++ b/frontend/src/pages/error/ErrorPage.tsx @@ -0,0 +1,51 @@ +import { Grid, Typography } from "@mui/material"; + +/** + * This component will be rendered when an error occurs. + * @param statusCode - The status code of the error + * @param statusTitle - The name of the error + * @param message - Additional information about the error + * @returns The ErrorPage component + */ +export function ErrorPage( + { statusCode, statusTitle, message }: { statusCode: string, statusTitle: string, message: string } +): React.JSX.Element { + return ( + + + + + + { statusCode } + + + + icon + + + + + + { statusTitle } + + + + + { message } + + + + ); +}