diff --git a/package-lock.json b/package-lock.json index d701c1a..b8f2bc9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@emotion/styled": "^11.10.6", "@mui/material": "^5.11.10", "@mui/styled-engine-sc": "^5.11.9", + "@reduxjs/toolkit": "^1.9.3", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -25,6 +26,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-query": "^3.39.3", + "react-redux": "^8.0.5", "react-router-dom": "^6.8.1", "react-scripts": "5.0.1", "sort-by": "^1.2.0", @@ -3502,6 +3504,29 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@reduxjs/toolkit": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.3.tgz", + "integrity": "sha512-GU2TNBQVofL09VGmuSioNPQIu6Ml0YLf4EJhgj0AvBadRlCGzUWet8372LjvO4fqKZF2vH1xU0htAa7BrK9pZg==", + "dependencies": { + "immer": "^9.0.16", + "redux": "^4.2.0", + "redux-thunk": "^2.4.2", + "reselect": "^4.1.7" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.0.2" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@remix-run/router": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.2.tgz", @@ -4257,6 +4282,15 @@ "@types/node": "*" } }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -4450,6 +4484,11 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz", "integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "node_modules/@types/ws": { "version": "8.5.4", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", @@ -14748,6 +14787,49 @@ } } }, + "node_modules/react-redux": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz", + "integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==", + "dependencies": { + "@babel/runtime": "^7.12.1", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", + "hoist-non-react-statics": "^3.3.2", + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^16.8 || ^17.0 || ^18.0", + "@types/react-dom": "^16.8 || ^17.0 || ^18.0", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0", + "react-native": ">=0.59", + "redux": "^4" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-redux/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -14928,6 +15010,22 @@ "node": ">=8" } }, + "node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, + "node_modules/redux-thunk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", + "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", + "peerDependencies": { + "redux": "^4" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -15070,6 +15168,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, + "node_modules/reselect": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz", + "integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A==" + }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -16720,6 +16823,14 @@ "requires-port": "^1.0.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index bec398c..03ba835 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "@emotion/styled": "^11.10.6", "@mui/material": "^5.11.10", "@mui/styled-engine-sc": "^5.11.9", + "@reduxjs/toolkit": "^1.9.3", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -20,6 +21,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-query": "^3.39.3", + "react-redux": "^8.0.5", "react-router-dom": "^6.8.1", "react-scripts": "5.0.1", "sort-by": "^1.2.0", diff --git a/src/Components/Api/Api.tsx b/src/Components/Api/Api.tsx index 007e5a1..a9478b3 100644 --- a/src/Components/Api/Api.tsx +++ b/src/Components/Api/Api.tsx @@ -1,5 +1,7 @@ import axios from "axios"; +// Note APIs + export function GetNotes() { return axios.get("http://localhost:8000/api/v1/notes/").then((response) => { return response.data; @@ -22,3 +24,80 @@ export function AddNote(note: note) { export function DeleteNote(id: number) { return axios.delete("http://localhost:8000/api/v1/notes/" + id + "/"); } + +// User APIs +export interface register { + email: string; + username: string; + password: string; +} + +export function UserRegister(register: register) { + return axios + .post("http://localhost:8000/api/v1/accounts/users/", register) + .then(async (response) => { + console.log(response.data); + return true; + }) + .catch((error) => { + console.log("Registration failed: " + error); + return false; + }); +} + +export interface user { + username: string; + password: string; +} + +export function UserLogin(user: user) { + return axios + .post("http://localhost:8000/api/v1/accounts/token/login/", user) + .then(async (response) => { + localStorage.setItem("token", JSON.stringify(response.data.auth_token)); + console.log( + "Login Success! Stored Token: ", + JSON.parse(localStorage.getItem("token") || "") + ); + return true; + }) + .catch((error) => { + console.log("Login Failed: " + error); + return false; + }); +} + +export function UserInfo() { + const token = JSON.parse(localStorage.getItem("token") || ""); + return axios + .get("http://localhost:8000/api/v1/accounts/users/me/", { + headers: { + Authorization: "Token " + token, + }, + }) + .then((response) => { + return response.data; + }) + .catch((error) => { + console.log("Error in fetching user data"); + console.log(error); + }); +} + +export interface activation { + uid: string; + token: string; +} + +export function UserActivate(activation: activation) { + return axios + .post("http://localhost:8000/api/v1/accounts/users/activation/", activation) + .then(async (response) => { + console.log("Activation Success"); + return true; + }) + .catch((error) => { + console.log("Activation failed: " + error); + return false; + }); +} diff --git a/src/Components/AppIcon/AppIcon.tsx b/src/Components/AppIcon/AppIcon.tsx new file mode 100644 index 0000000..56d5c80 --- /dev/null +++ b/src/Components/AppIcon/AppIcon.tsx @@ -0,0 +1,30 @@ +import * as React from "react"; + +export interface props { + size: number; + color: string; +} + +export default function AppIcon(props: props) { + return ( + <> + + + + + + + + + ); +} diff --git a/src/Components/Header/Header.tsx b/src/Components/Header/Header.tsx index 3e86d14..687ce92 100644 --- a/src/Components/Header/Header.tsx +++ b/src/Components/Header/Header.tsx @@ -1,11 +1,45 @@ import * as React from "react"; import styles from "../../styles"; +import AppIcon from "../AppIcon/AppIcon"; +import Login from "../LoginButton/LoginButton"; +import HomeIcon from "../HomeIcon/HomeIcon"; export default function Header() { return (
-

React - A Notes Demo

+
+
+ + + + +
+
+ +

Clip Notes

+
+
+
+ +
+
+
); } diff --git a/src/Components/HomeIcon/HomeIcon.tsx b/src/Components/HomeIcon/HomeIcon.tsx new file mode 100644 index 0000000..38e6d07 --- /dev/null +++ b/src/Components/HomeIcon/HomeIcon.tsx @@ -0,0 +1,44 @@ +import { Button } from "@mui/material"; +import * as React from "react"; +import { useNavigate } from "react-router-dom"; + +export interface props { + size: number; + color: string; +} + +export default function HomeIcon(props: props) { + const navigate = useNavigate(); + return ( + + ); +} diff --git a/src/Components/LoginButton/LoginButton.tsx b/src/Components/LoginButton/LoginButton.tsx new file mode 100644 index 0000000..cb1746f --- /dev/null +++ b/src/Components/LoginButton/LoginButton.tsx @@ -0,0 +1,61 @@ +import * as React from "react"; +import { Button } from "@mui/material"; +import { useNavigate } from "react-router-dom"; +import styles from "../../styles"; +import { useDispatch, useSelector } from "react-redux"; +import { SetLoggedOut } from "../../Features/Redux/Slices/LoginSlice/LoginSlice"; +import { UnsetUser } from "../../Features/Redux/Slices/LoggedInUserSlice/LoggedInUserSlice"; + +export interface user { + LoggedInUser: { + value: { + email: string; + id: number; + username: string; + }; + }; +} + +export default function LoginButton() { + const dispatch = useDispatch(); + const logged_in = useSelector( + (state: { Login: { logged_in: boolean } }) => state.Login.logged_in + ); + const logged_in_user = useSelector((state: user) => state.LoggedInUser.value); + const navigate = useNavigate(); + if (!logged_in) { + return ( +
+

Not Logged in

+
+ +
+ ); + } + return ( +
+

Logged in as {logged_in_user.username}

+
+ +
+ ); +} diff --git a/src/Components/Note/Note.tsx b/src/Components/Note/Note.tsx index 346a5d5..153e424 100644 --- a/src/Components/Note/Note.tsx +++ b/src/Components/Note/Note.tsx @@ -27,7 +27,7 @@ export default function Note(props: props) {

Timestamp: {props.date_created}

+

{error}

+ +
+ + ); +} diff --git a/src/Routes/NewNote/NewNote.tsx b/src/Routes/NewNote/NewNote.tsx index 12485e9..77c3510 100644 --- a/src/Routes/NewNote/NewNote.tsx +++ b/src/Routes/NewNote/NewNote.tsx @@ -48,7 +48,7 @@ export default function NewNote() { /> +

{feedback}

+ + + ); +} diff --git a/src/index.tsx b/src/index.tsx index 320d3b5..2adba04 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -6,9 +6,15 @@ import { createBrowserRouter, RouterProvider } from "react-router-dom"; import Home from "./Routes/Home/Home"; import NewNote from "./Routes/NewNote/NewNote"; +import Login from "./Routes/Login/Login"; +import Activation from "./Routes/Activation/Activation"; +import Register from "./Routes/Register/Register"; import { QueryClient, QueryClientProvider } from "react-query"; +import { Provider } from "react-redux"; +import Store from "./Features/Redux/Store/Store"; + const queryClient = new QueryClient(); const router = createBrowserRouter([ @@ -20,6 +26,18 @@ const router = createBrowserRouter([ path: "/NewNote", element: , }, + { + path: "/Login", + element: , + }, + { + path: "/Register", + element: , + }, + { + path: "/Activation/:uid/:token", + element: , + }, ]); const root = ReactDOM.createRoot( @@ -28,9 +46,11 @@ const root = ReactDOM.createRoot( root.render( - - - + + + + + ); diff --git a/src/styles.tsx b/src/styles.tsx index 9bfd598..19fac7a 100644 --- a/src/styles.tsx +++ b/src/styles.tsx @@ -13,10 +13,26 @@ const styles: { [key: string]: React.CSSProperties } = { position: "sticky", backgroundColor: "#0087e4", margin: "2vh", - height: "8vh", + height: "32px", padding: 8, borderRadius: 4, }, + header_contentwrapper: { + display: "flex", + alignItems: "center", + justifyContent: "center", + }, + window: { + alignSelf: "center", + width: "60%", + minHeight: "128px", + display: "flex", + flexDirection: "column", + backgroundColor: "#0087e4", + margin: 16, + padding: 16, + borderRadius: 16, + }, note: { display: "flex", flexDirection: "column", @@ -49,7 +65,7 @@ const styles: { [key: string]: React.CSSProperties } = { borderColor: "#00293e", outline: "none", color: "white", - height: "3vh", + height: "16px", fontSize: "2vh", fontWeight: "bold", }, @@ -63,23 +79,37 @@ const styles: { [key: string]: React.CSSProperties } = { color: "white", height: "100%", width: "100%", - maxHeight: "100vh", + minWidth: "100%", + maxHeight: "200px", fontSize: "2vh", fontWeight: "bold", }, - button_add: { + button_green: { backgroundColor: "#0dbc6a", alignSelf: "center", display: "flex", flexDirection: "row", width: "256x", + marginTop: 4, + marginBottom: 4, }, - button_remove: { + button_yellow: { + backgroundColor: "#e2b465", + alignSelf: "center", + display: "flex", + flexDirection: "row", + width: "256px", + marginTop: 4, + marginBottom: 4, + }, + button_red: { backgroundColor: "#bc231e", alignSelf: "center", display: "flex", flexDirection: "row", width: "256px", + marginTop: 4, + marginBottom: 4, }, text_small: { color: "white", @@ -87,17 +117,36 @@ const styles: { [key: string]: React.CSSProperties } = { fontWeight: "bold", textAlign: "center", }, + text_small_red: { + color: "#bc231e", + fontSize: "2vh", + fontWeight: "bold", + textAlign: "center", + }, + text_small_green: { + color: "#0dbc6a", + fontSize: "2vh", + fontWeight: "bold", + textAlign: "center", + }, text_medium: { color: "white", fontSize: "4vh", fontWeight: "bold", textAlign: "center", }, + text_medium_red: { + color: "#bc231e", + fontSize: "4vh", + fontWeight: "bold", + textAlign: "center", + }, flex_column: { display: "flex", flexDirection: "column", }, flex_row: { + width: "100%", justifyContent: "center", display: "flex", flexDirection: "row",