Compare commits
	
		
			3 Commits
		
	
	
		
			8bc62416ec
			...
			71cb9bdff6
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						71cb9bdff6
	
				 | 
					
					
						|||
| 
						
						
							
						
						a73afc90d9
	
				 | 
					
					
						|||
| 
						
						
							
						
						046726de11
	
				 | 
					
					
						
@@ -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`.
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										74
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										74
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -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",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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();
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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>
 | 
					 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										25
									
								
								frontend/src/AuthContext.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								frontend/src/AuthContext.tsx
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										9
									
								
								frontend/src/Router.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					import { BrowserRouter, Routes } from "react-router-dom";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Router = () => {
 | 
				
			||||||
 | 
					  <BrowserRouter>
 | 
				
			||||||
 | 
					    <Routes>{}</Routes>
 | 
				
			||||||
 | 
					  </BrowserRouter>;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Router;
 | 
				
			||||||
							
								
								
									
										22
									
								
								frontend/src/components/RequireAuth.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								frontend/src/components/RequireAuth.tsx
									
									
									
									
									
										Normal 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;
 | 
				
			||||||
							
								
								
									
										16
									
								
								frontend/src/components/ScrollToTop.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								frontend/src/components/ScrollToTop.tsx
									
									
									
									
									
										Normal 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;
 | 
				
			||||||
							
								
								
									
										19
									
								
								frontend/src/components/Title.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								frontend/src/components/Title.tsx
									
									
									
									
									
										Normal 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;
 | 
				
			||||||
@@ -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;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user