Compare commits

..

3 Commits

Author SHA1 Message Date
71cb9bdff6
Feat(Frontend): Added Routing 2023-07-21 22:37:28 -04:00
a73afc90d9
Feat(Frontend): Authentication Context 2023-07-21 22:22:19 -04:00
046726de11
Fix(Frontend): Set maxWidth to md
- This will make sure Material UI's Container will not expand to fill
  the entire screen and instead just stop expanding at around 960px
  (which is `md`).
2023-07-21 18:23:34 -04:00
11 changed files with 212 additions and 23 deletions

View File

@ -21,10 +21,18 @@ useMemo is a React Hook that lets you cache the result of a calculation between
Change default component colors to suit one's needs. Change default component colors to suit one's needs.
#### CSSBaseline ##### CSSBaseline
Reset the CSS injected into `<head>`. A collection of HTML element and attribute style-normalizations, you can expect all of the elements to look the same across all browsers. Reset the CSS injected into `<head>`. A collection of HTML element and attribute style-normalizations, you can expect all of the elements to look the same across all browsers.
#### useMediaQuery ##### useMediaQuery
This React hook listens for matches to a CSS media query. It allows the rendering of components based on whether the query matches or not. This React hook listens for matches to a CSS media query. It allows the rendering of components based on whether the query matches or not.
##### Container's maxWidth
This will make sure Material UI's Container will not expand to fill the entire screen and instead just stop expanding at around 960px (which is `md`).
### Configure the page's title (Browser Tab Text)
Use `react-helmet-async`, wrap it around the `App` in `App.tsx`.

View File

@ -22,6 +22,8 @@
"@types/react": "^18.2.15", "@types/react": "^18.2.15",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-helmet-async": "^1.3.0",
"react-router-dom": "^6.14.2",
"react-scripts": "^5.0.1", "react-scripts": "^5.0.1",
"typescript": "^4.9.5", "typescript": "^4.9.5",
"web-vitals": "^2.1.4", "web-vitals": "^2.1.4",
@ -3742,6 +3744,14 @@
"url": "https://opencollective.com/popperjs" "url": "https://opencollective.com/popperjs"
} }
}, },
"node_modules/@remix-run/router": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.7.2.tgz",
"integrity": "sha512-7Lcn7IqGMV+vizMPoEl5F0XDshcdDYtMI6uJLQdQz5CfZAwy3vvGKYSUk789qndt5dEC4HfSjviSYlSoHGL2+A==",
"engines": {
"node": ">=14"
}
},
"node_modules/@rollup/plugin-babel": { "node_modules/@rollup/plugin-babel": {
"version": "5.3.1", "version": "5.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
@ -9934,6 +9944,14 @@
"node": ">= 0.4" "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==",
"dependencies": {
"loose-envify": "^1.0.0"
}
},
"node_modules/ipaddr.js": { "node_modules/ipaddr.js": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz",
@ -15299,6 +15317,27 @@
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz",
"integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg=="
}, },
"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=="
},
"node_modules/react-helmet-async": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-1.3.0.tgz",
"integrity": "sha512-9jZ57/dAn9t3q6hneQS0wukqC2ENOBgMNVEhb/ZG9ZSxUetzVIw4iAmEU38IaVg3QGYauQPhSeUTuIUtFglWpg==",
"dependencies": {
"@babel/runtime": "^7.12.5",
"invariant": "^2.2.4",
"prop-types": "^15.7.2",
"react-fast-compare": "^3.2.0",
"shallowequal": "^1.1.0"
},
"peerDependencies": {
"react": "^16.6.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.6.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-is": { "node_modules/react-is": {
"version": "17.0.2", "version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
@ -15312,6 +15351,36 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/react-router": {
"version": "6.14.2",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.14.2.tgz",
"integrity": "sha512-09Zss2dE2z+T1D03IheqAFtK4UzQyX8nFPWx6jkwdYzGLXd5ie06A6ezS2fO6zJfEb/SpG6UocN2O1hfD+2urQ==",
"dependencies": {
"@remix-run/router": "1.7.2"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"react": ">=16.8"
}
},
"node_modules/react-router-dom": {
"version": "6.14.2",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.14.2.tgz",
"integrity": "sha512-5pWX0jdKR48XFZBuJqHosX3AAHjRAzygouMTyimnBPOLdY3WjzUSKhus2FVMihUFWzeLebDgr4r8UeQFAct7Bg==",
"dependencies": {
"@remix-run/router": "1.7.2",
"react-router": "6.14.2"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"react": ">=16.8",
"react-dom": ">=16.8"
}
},
"node_modules/react-scripts": { "node_modules/react-scripts": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
@ -16154,6 +16223,11 @@
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
}, },
"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=="
},
"node_modules/shebang-command": { "node_modules/shebang-command": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",

View File

@ -17,6 +17,8 @@
"@types/react": "^18.2.15", "@types/react": "^18.2.15",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-helmet-async": "^1.3.0",
"react-router-dom": "^6.14.2",
"react-scripts": "^5.0.1", "react-scripts": "^5.0.1",
"typescript": "^4.9.5", "typescript": "^4.9.5",
"web-vitals": "^2.1.4", "web-vitals": "^2.1.4",

View File

@ -1,9 +1,7 @@
import React from "react"; import React from "react";
import { render, screen } from "@testing-library/react"; import { render } from "@testing-library/react";
import App from "./App"; import App from "./App";
test("renders learn react link", () => { test("renders the app", () => {
render(<App />); render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
}); });

View File

@ -1,29 +1,41 @@
import React from "react"; import React from "react";
import logo from "./logo.svg";
import "./App.css"; import "./App.css";
// Roboto font and its weights
import "@fontsource/roboto/300.css"; import "@fontsource/roboto/300.css";
import "@fontsource/roboto/400.css"; import "@fontsource/roboto/400.css";
import "@fontsource/roboto/500.css"; import "@fontsource/roboto/500.css";
import "@fontsource/roboto/700.css"; import "@fontsource/roboto/700.css";
// Theming
import ThemeProvider from "./ThemeProvider";
// Material UI stuffs
import Container from "@mui/material/Container";
// React helmet async: configure the page's title
import { Helmet, HelmetProvider } from "react-helmet-async";
// Authentication Context: Check if user is logged in or not
import { AuthContextProvider } from "./AuthContext";
// React router
import Router from "./Router";
function App() { function App() {
return ( return (
<div className="App"> <AuthContextProvider>
<header className="App-header"> <HelmetProvider>
<img src={logo} className="App-logo" alt="logo" /> <Helmet>
<p> <title>Todo</title>
Edit <code>src/App.tsx</code> and save to reload. </Helmet>
</p> <ThemeProvider>
<a <Container maxWidth="md">
className="App-link" <Router />
href="https://reactjs.org" </Container>
target="_blank" </ThemeProvider>
rel="noopener noreferrer" </HelmetProvider>
> </AuthContextProvider>
Learn React
</a>
</header>
</div>
); );
} }

View File

@ -0,0 +1,25 @@
import { createContext, useState } from "react";
interface IAuth {
authenticated: boolean;
setAuthenticated: (value: boolean) => void;
}
export const AuthContext = createContext<IAuth>({
authenticated: true,
setAuthenticated: (value: boolean) => {},
});
interface IProps {
children?: React.ReactNode;
}
export const AuthContextProvider = ({ children }: IProps) => {
const [authenticated, setAuthenticated] = useState(true);
return (
<AuthContext.Provider value={{ authenticated, setAuthenticated }}>
{children}
</AuthContext.Provider>
);
};

9
frontend/src/Router.tsx Normal file
View File

@ -0,0 +1,9 @@
import { BrowserRouter, Routes } from "react-router-dom";
const Router = () => {
<BrowserRouter>
<Routes>{}</Routes>
</BrowserRouter>;
};
export default Router;

View File

@ -0,0 +1,22 @@
import { useContext } from "react";
import { Navigate, useLocation } from "react-router-dom";
import { AuthContext } from "src/AuthContext";
interface IProps {
children: React.ReactNode;
}
const RequireAuth = ({ children }: IProps) => {
const { authenticated } = useContext(AuthContext);
const location = useLocation();
if (authenticated) {
return <>{children}</>;
} else {
// re-route user back to login page if not logged in
return <Navigate state={{ from: location }} to="/login/" />;
}
};
export default RequireAuth;

View File

@ -0,0 +1,16 @@
import { useEffect } from "react";
import { useLocation } from "react-router";
const ScrollToTop = () => {
const { pathname } = useLocation();
// pathname changes => scroll to top
// scrolling only on navigation
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
return null;
};
export default ScrollToTop;

View File

@ -0,0 +1,19 @@
import Typography from "@mui/material/Typography";
import { Helmet } from "react-helmet-async";
interface IProps {
title: string;
}
const Title = ({ title }: IProps) => (
<>
<Helmet>
<title>Todo | {title}</title>
</Helmet>
<Typography component="h1" variant="h5">
{title}
</Typography>
</>
);
export default Title;

View File

@ -3,3 +3,7 @@
// expect(element).toHaveTextContent(/react/i) // expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom // learn more: https://github.com/testing-library/jest-dom
import "@testing-library/jest-dom"; import "@testing-library/jest-dom";
window.scrollTo = (x, y) => {
document.documentElement.scrollTop = y;
};