diff --git a/.env b/.env new file mode 100644 index 0000000000000000000000000000000000000000..f92bf9c9fe269667b7d97c1a674e32738d735b72 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +VITE_APP_NAME=ZBH Portal +VITE_PAGE_AFTER_LOGIN=/ diff --git a/package-lock.json b/package-lock.json index 60347a3a2611f3af9e8e301e78d805b1ea58ff91..b901065ff477c239a95a3ebbf4aeefd54b887e99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "hamburger-react": "^2.5.1", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-helmet-async": "^2.0.5", "react-icons": "^5.2.1", "react-router-dom": "^6.24.0" }, @@ -3550,6 +3551,15 @@ "node": ">= 0.4" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/is-array-buffer": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", @@ -4935,6 +4945,26 @@ "react": "^18.3.1" } }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", + "license": "MIT" + }, + "node_modules/react-helmet-async": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-2.0.5.tgz", + "integrity": "sha512-rYUYHeus+i27MvFE+Jaa4WsyBKGkL6qVgbJvSBoX8mbsWoABJXdEO0bZyi0F6i+4f0NuIb8AvqPMj3iXFHkMwg==", + "license": "Apache-2.0", + "dependencies": { + "invariant": "^2.2.4", + "react-fast-compare": "^3.2.2", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": "^16.6.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-icons": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.2.1.tgz", @@ -5281,6 +5311,12 @@ "node": ">= 0.4" } }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/package.json b/package.json index 4dd3acfc5d2b4d08d3e8185e7a8e1a918b62f869..723c6e77d3aa3e488a18592d87587c21355c595a 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "hamburger-react": "^2.5.1", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-helmet-async": "^2.0.5", "react-icons": "^5.2.1", "react-router-dom": "^6.24.0" }, diff --git a/src/App.jsx b/src/App.jsx index e5f437116d08a04c58d8257aa172c9addc51049d..2075f13e105e32caaa8a2588bf111a5d01ecc55e 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,12 +1,16 @@ import { createBrowserRouter, RouterProvider } from 'react-router-dom'; import { sitemap } from "./routes/Sitemap"; +import { HelmetProvider } from 'react-helmet-async'; function App() { let pages = createBrowserRouter(sitemap); return ( - <RouterProvider router={pages} /> + <HelmetProvider> + <RouterProvider router={pages} /> + </HelmetProvider> + ); } diff --git a/src/layouts/CleanLayout.jsx b/src/layouts/CleanLayout.jsx new file mode 100644 index 0000000000000000000000000000000000000000..ab0e1eef67a1dad410f929d03605b79102bf5654 --- /dev/null +++ b/src/layouts/CleanLayout.jsx @@ -0,0 +1,43 @@ +import React, { useEffect } from 'react'; +import { Outlet } from 'react-router-dom'; +import Header from './partials/Header'; + +function CleanLayout() { + // ################################# + // HOOKS + // ################################# + // SET BODY CLASSES + useEffect(() => { + // ### on run exec this code + document.body.className = ''; + document.body.classList.add('overflow-x-hidden', 'h-full', 'flex', 'justify-center', 'relative'); + + document.getElementById('root').classList.remove('grid-rows-[auto_1fr_auto]', 'min-h-full', 'w-screen', 'max-h-full', 'sm:grid-rows-[auto_auto_auto_1fr]'); + document.getElementById('root').classList.add('grid-rows-[auto_1fr]', 'h-full'); + }, []); + + // ################################# + // FUNCTIONS + // ################################# + + // ################################# + // OUTPUT + // ################################# + return ( + <> + <div className="grid grid-cols-1 grid-rows-[auto_1fr] h-full min-w-xs text-UhhGrey"> + <Header layout="clean" /> + <main role="main"> + <div className="box-border overflow-x-hidden overflow-y-auto px-4 mt-2 "> + <div> + <Outlet /> + </div> + </div> + </main> + </div> + + </> + ); +} + +export default React.memo(CleanLayout); \ No newline at end of file diff --git a/src/pages/Err404.jsx b/src/pages/Err404.jsx new file mode 100644 index 0000000000000000000000000000000000000000..c71885930ef2083fdd42232b1bf83d55123a8e01 --- /dev/null +++ b/src/pages/Err404.jsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { Helmet } from 'react-helmet-async'; +import { useNavigate } from "react-router-dom"; + +function Err404() { + // ################################# + // HOOKS + // ################################# + const redirect = useNavigate(); + // ################################# + // FUNCTIONS + // ################################# + function handleClick(event) { + event.preventDefault(); + redirect(-1); + } + + // ################################# + // OUTPUT + // ################################# + return ( + <> + {/* render page title */} + <Helmet><title>[{import.meta.env.VITE_APP_NAME}] Error</title></Helmet> + <h1 className="text-8xl text-center text-UhhGrey">404</h1> + <h2>Page not found</h2> + <p className="mt-8">We're sorry for the inconvenience, but we can lead you</p> + <span className="inline-block w-full h-16 px-8 mb-4 border-box leading-[4rem] text-center align-middle bg-UhhBlue text-UhhWhite font-UhhBC cursor-pointer" onClick={handleClick}>back</span> + </> + ); +} + +export default React.memo(Err404); diff --git a/src/routes/Sitemap.jsx b/src/routes/Sitemap.jsx index 3d0fe9a5f9e25ef3adbdf19d21cd7c0808a6d0ee..0635aed19632fa99fd80dbfc38a8118338c7ee0e 100644 --- a/src/routes/Sitemap.jsx +++ b/src/routes/Sitemap.jsx @@ -1,6 +1,8 @@ import MainLayout from "../layouts/MainLayout"; import { loadComponent } from "./WrapRoutes"; import { Link } from 'react-router-dom'; +import CleanLayout from '../layouts/CleanLayout'; + export const sitemap = [{ title: 'MenuBar', element: <MainLayout />, @@ -24,4 +26,9 @@ export const sitemap = [{ ] } ] +}, { + title: 'Others', element: <CleanLayout />, children: [ + // ERROR + { path: '*', element: loadComponent('Err404') } + ] }]; \ No newline at end of file