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;
|
||||||
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user