From b59d56f2aaa4a746bfd81fd66af99db5b839173c Mon Sep 17 00:00:00 2001 From: Soustab Haldar Date: Mon, 27 Jun 2022 13:40:10 +0530 Subject: [PATCH 1/3] all tasks done --- components/AddTask.js | 69 ++++-- components/LoginForm.js | 136 +++++++++--- components/Nav.js | 149 ++++++++----- components/RegisterForm.js | 206 +++++++++--------- components/TodoListItem.js | 150 +++++++++---- context/auth.js | 54 ++--- middlewares/auth_required.js | 14 ++ middlewares/no_auth_required.js | 16 +- package.json | 5 + pages/_app.js | 20 +- pages/index.js | 91 ++++++-- pages/toast.js | 26 +++ pages/useDarkMode.js | 43 ++++ styles/globals.css | 30 ++- tailwind.config.js | 18 +- yarn.lock | 369 +++++++++++++++++++++++++++++++- 16 files changed, 1105 insertions(+), 291 deletions(-) create mode 100644 pages/toast.js create mode 100644 pages/useDarkMode.js diff --git a/components/AddTask.js b/components/AddTask.js index 9652adb..8983a2f 100644 --- a/components/AddTask.js +++ b/components/AddTask.js @@ -1,25 +1,66 @@ -export default function AddTask() { +import { useState } from "react"; +import axios from "../utils/axios"; +import { useAuth } from "../context/auth"; +import { API_URL } from "../utils/constants"; +import { + displayErrorToast, + displaySuccessToast, + displayWarningToast, +} from "../pages/toast"; + +export default function AddTask(props) { + /** + * @todo Complete this function. + * @todo 1. Send the request to add the task to the backend server. + * @todo 2. Add the task in the dom. + */ + + const { token } = useAuth(); + const [task, setTask] = useState(""); + const addTask = () => { - /** - * @todo Complete this function. - * @todo 1. Send the request to add the task to the backend server. - * @todo 2. Add the task in the dom. - */ - } + if (task == "") { + displayWarningToast("Task title is required!"); + } else { + const dataForApiRequest = { + title: task, + }; + axios({ + headers: { + Authorization: "Token " + token, + }, + url: API_URL + "todo/create/", + method: "POST", + data: dataForApiRequest, + }) + .then((res) => { + setTask(""); + props.displayTasks(); + + displaySuccessToast("Task added successfully"); + }) + .catch(function (err) { + displayErrorToast("Error!! Task could not be added!"); + }); + } + }; + return ( -
+
setTask(e.target.value)} + type="text" + className="todo-add-task-input px-4 py-2 placeholder-blueGray-300 dark:placeholder-gray-100 text-blueGray-600 dark:bg-gray-400 dark:text-white bg-white rounded text-sm border border-blueGray-300 outline-none focus:outline-none focus:ring w-full " + placeholder="Enter Task Title" + value={task} />
- ) + ); } diff --git a/components/LoginForm.js b/components/LoginForm.js index fa28f9e..a3ec611 100644 --- a/components/LoginForm.js +++ b/components/LoginForm.js @@ -1,4 +1,37 @@ +import React from "react"; +import axios from "../utils/axios"; +import { API_URL } from "../utils/constants"; +import { useAuth } from "../context/auth"; +import { useRouter } from "next/router"; +import { no_auth_required } from "../middlewares/no_auth_required"; +import { ToastContainer, toast } from "react-toastify"; +import "react-toastify/dist/ReactToastify.css"; +import { + displayErrorToast, + displaySuccessToast, + displayWarningToast, +} from "../pages/toast"; + export default function RegisterForm() { + no_auth_required(); + const router = useRouter(); + const { setToken } = useAuth(); + + const [loginData, setLoginData] = React.useState({ + usernameInput: "", + passwordInput: "", + }); + + function handleChange(e) { + const { name, value } = e.target; + setLoginData((prev) => { + return { + ...prev, + [name]: value, + }; + }); + } + const login = () => { /*** * @todo Complete this function. @@ -6,38 +39,81 @@ export default function RegisterForm() { * @todo 2. Fetch the auth token from backend and login the user. * @todo 3. Set the token in the context (See context/auth.js) */ - } + + const inputData = { + username: loginData.usernameInput, + password: loginData.passwordInput, + }; + + if (inputData.username == "" || inputData.password == "") { + displayWarningToast("Please fill all required fields!"); + return; + } + + axios({ + url: API_URL + "auth/login/", + method: "POST", + data: inputData, + }) + .then((res) => { + displaySuccessToast("User logged in successfully"); + const token = res.data.token; + setToken(token); + router.push("LOGIN", "/"); + }) + .catch(function (err) { + displayErrorToast("Invalid credentials! Please try again."); + setLoginData({ + usernameInput: "", + passwordInput: "", + }); + }); + }; return ( -
-
-
-

Login

- - - - - + <> +
+
+
+

Login

+ + + + + +
-
- ) + + + ); } diff --git a/components/Nav.js b/components/Nav.js index e03cb0f..23c9362 100644 --- a/components/Nav.js +++ b/components/Nav.js @@ -1,62 +1,113 @@ /* eslint-disable jsx-a11y/alt-text */ /* eslint-disable @next/next/no-img-element */ -import Link from 'next/link' -import { useAuth } from '../context/auth' +import Link from "next/link"; +import { useAuth } from "../context/auth"; +import TaskIcon from "@mui/icons-material/Task"; +import LoginIcon from "@mui/icons-material/Login"; +import HowToRegIcon from "@mui/icons-material/HowToReg"; +import LightModeIcon from "@mui/icons-material/LightMode"; +import DarkModeIcon from "@mui/icons-material/DarkMode"; +import LogoutIcon from "@mui/icons-material/Logout"; +import useDarkMode from "../pages/useDarkMode"; /** * * @todo Condtionally render login/register and Profile name in NavBar */ export default function Nav() { - const { logout, profileName, avatarImage } = useAuth() - + const { logout, profileName, avatarImage, token } = useAuth(); + const [darkTheme, setDarkTheme] = useDarkMode(); + const ThemeIcon = () => { + const handleMode = () => setDarkTheme(!darkTheme); + return ( + + {darkTheme ? ( + + ) : ( + + )} + + ); + }; return ( - - ) + ) : ( +
+

+ +

+ +
+ + +
+
+ )} + + +
+ ); } diff --git a/components/RegisterForm.js b/components/RegisterForm.js index a6ef2e3..702f23c 100644 --- a/components/RegisterForm.js +++ b/components/RegisterForm.js @@ -1,17 +1,24 @@ -import React, { useState } from 'react' -import axios from '../utils/axios' -import { useAuth } from '../context/auth' -import { useRouter } from 'next/router' +import React, { useState } from "react"; +import axios from "../utils/axios"; +import { useAuth } from "../context/auth"; +import { useRouter } from "next/router"; +import { ToastContainer, toast } from "react-toastify"; +import "react-toastify/dist/ReactToastify.css"; +import { + displaySuccessToast, + displayErrorToast, + displayWarningToast, +} from "../pages/toast"; export default function Register() { - const { setToken } = useAuth() - const router = useRouter() + const { setToken } = useAuth(); + const router = useRouter(); - const [firstName, setFirstName] = useState('') - const [lastName, setLastName] = useState('') - const [email, setEmail] = useState('') - const [password, setPassword] = useState('') - const [username, setUsername] = useState('') + const [firstName, setFirstName] = useState(""); + const [lastName, setLastName] = useState(""); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [username, setUsername] = useState(""); const registerFieldsAreValid = ( firstName, @@ -21,116 +28,123 @@ export default function Register() { password ) => { if ( - firstName === '' || - lastName === '' || - email === '' || - username === '' || - password === '' + firstName === "" || + lastName === "" || + email === "" || + username === "" || + password === "" ) { - console.log('Please fill all the fields correctly.') - return false + displayWarningToast("Please fill all required fields!"); + return false; } if (!/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(email)) { - console.log('Please enter a valid email address.') - return false + displayWarningToast("Please enter a valid email address."); + + return false; } - return true - } + return true; + }; const register = (e) => { - e.preventDefault() + e.preventDefault(); if ( registerFieldsAreValid(firstName, lastName, email, username, password) ) { - console.log('Please wait...') - const dataForApiRequest = { - name: firstName + ' ' + lastName, + name: firstName + " " + lastName, email: email, username: username, password: password, - } + }; - axios.post( - 'auth/register/', - dataForApiRequest, - ) + axios + .post("auth/register/", dataForApiRequest) .then(function ({ data, status }) { - setToken(data.token) - router.push('/') + setToken(data.token); + router.push("LOGIN", "/"); }) .catch(function (err) { - console.log( - 'An account using same email or username is already created' - ) - }) + displayErrorToast("Account with same Username already exists!"); + setUsername(""); + setPassword(""); + }); } - } + }; return ( -
-
-
-

Register

- setFirstName(e.target.value)} - placeholder='First Name' - /> - setLastName(e.target.value)} - placeholder='Last Name' - /> + <> +
+
+
+

Register

+ setFirstName(e.target.value)} + placeholder="First Name" + /> + setLastName(e.target.value)} + placeholder="Last Name" + /> - setEmail(e.target.value)} - placeholder='Email Address' - /> + setEmail(e.target.value)} + placeholder="Email Address" + /> - setUsername(e.target.value)} - placeholder='Username' - /> + setUsername(e.target.value)} + placeholder="Username" + /> - setPassword(e.target.value)} - placeholder='Password' - /> + setPassword(e.target.value)} + placeholder="Password" + /> - + +
-
- ) + + + ); } diff --git a/components/TodoListItem.js b/components/TodoListItem.js index 7965f3b..470bd10 100644 --- a/components/TodoListItem.js +++ b/components/TodoListItem.js @@ -1,12 +1,41 @@ /* eslint-disable @next/next/no-img-element */ -export default function TodoListItem() { +import React, { useState } from "react"; +import { useAuth } from "../context/auth"; +import axios from "../utils/axios"; +import { API_URL } from "../utils/constants"; +import { + displayErrorToast, + displaySuccessToast, + displayWarningToast, +} from "../pages/toast"; +import useDarkMode from "../pages/useDarkMode"; + +import DeleteIcon from "@mui/icons-material/Delete"; +import ModeEditIcon from "@mui/icons-material/ModeEdit"; + +export default function TodoListItem(props) { + const [task, setTask] = useState(props.task); + const { token } = useAuth(); + + const [darkTheme, setDarkTheme] = useDarkMode(); + + const inputId = "input-button-" + props.id; + const doneId = "done-button-" + props.id; + const taskId = "task-" + props.id; + const taskActionsId = "task-actions-" + props.id; + const editTask = (id) => { /** * @todo Complete this function. * @todo 1. Update the dom accordingly */ - } + document.getElementById("done-button-" + id).classList.remove("hideme"); + document.getElementById("input-button-" + id).classList.remove("hideme"); + + document.getElementById("task-actions-" + id).classList.add("hideme"); + document.getElementById("task-" + id).classList.add("hideme"); + }; const deleteTask = (id) => { /** @@ -14,7 +43,21 @@ export default function TodoListItem() { * @todo 1. Send the request to delete the task to the backend server. * @todo 2. Remove the task from the dom. */ - } + axios({ + headers: { + Authorization: "Token " + token, + }, + url: API_URL + "todo/" + id + "/", + method: "DELETE", + }) + .then(() => { + displaySuccessToast("Task deleted successfully!"); + props.displayTasks(); + }) + .catch((err) => { + displayErrorToast("Something went wrong!"); + }); + }; const updateTask = (id) => { /** @@ -22,57 +65,92 @@ export default function TodoListItem() { * @todo 1. Send the request to update the task to the backend server. * @todo 2. Update the task in the dom. */ - } + if (task === "") { + displayWarningToast("Task title cannot be empty!"); + } else { + axios({ + headers: { + Authorization: "Token " + token, + }, + url: API_URL + "todo/" + id + "/", + method: "PUT", + data: { + title: task, + }, + }) + .then(() => { + displaySuccessToast("Todo has been successfully updated..."); + document + .getElementById("input-button-" + props.id) + .classList.add("hideme"); + document + .getElementById("done-button-" + props.id) + .classList.add("hideme"); + document + .getElementById("task-" + props.id) + .classList.remove("hideme"); + document + .getElementById("task-actions-" + props.id) + .classList.remove("hideme"); + props.displayTasks(); + }) + .catch(function (err) { + displayErrorToast("Something went wrong!"); + }); + } + }; return ( <> -
  • +
  • setTask(e.target.value)} + id={inputId} + type="text" + className="hideme appearance-none dark:bg-gray-500 rounded w-full py-2 px-3 dark:text-gray-100 text-gray-700 leading-tight focus:outline-none focus:ring todo-edit-task-input" + placeholder="Edit The Task" + value={task} /> -
    +
    -
    - Sample Task 1 +
    + {task}
    - + +
  • - ) + ); } diff --git a/context/auth.js b/context/auth.js index 646b10e..a44a391 100644 --- a/context/auth.js +++ b/context/auth.js @@ -1,45 +1,45 @@ -import { useEffect, useState, useContext, createContext } from 'react' -import { useCookies } from 'react-cookie' -import axios from '../utils/axios' -import { useRouter } from 'next/router' +import { useEffect, useState, useContext, createContext } from "react"; +import { useCookies } from "react-cookie"; +import axios from "../utils/axios"; +import { useRouter } from "next/router"; -const AuthContext = createContext({}) +const AuthContext = createContext({}); export const AuthProvider = ({ children }) => { - const router = useRouter() - const [profileName, setProfileName] = useState('') - const [avatarImage, setAvatarImage] = useState('#') - const [cookies, setCookies, removeCookies] = useCookies(['auth']) - const token = cookies.token + const router = useRouter(); + const [profileName, setProfileName] = useState(""); + const [avatarImage, setAvatarImage] = useState("#"); + const [cookies, setCookies, removeCookies] = useCookies(["auth"]); + const token = cookies.token; - const setToken = (newToken) => setCookies('token', newToken, { path: '/' }) - const deleteToken = () => removeCookies('token') + const setToken = (newToken) => setCookies("token", newToken, { path: "/" }); + const deleteToken = () => removeCookies("token"); const logout = () => { - deleteToken() - router.push('/login') - } + deleteToken(); + router.reload(); + }; useEffect(() => { if (token) { axios - .get('auth/profile/', { + .get("auth/profile/", { headers: { - Authorization: 'Token ' + token, + Authorization: "Token " + token, }, }) .then((response) => { setAvatarImage( - 'https://ui-avatars.com/api/?name=' + + "https://ui-avatars.com/api/?name=" + response.data.name + - '&background=fff&size=33&color=007bff' - ) - setProfileName(response.data.name) + "&background=fff&size=33&color=007bff" + ); + setProfileName(response.data.name); }) .catch((error) => { - console.log('Some error occurred') - }) + console.log("Some error occurred"); + }); } - }, [setAvatarImage, setProfileName, token]) + }, [setAvatarImage, setProfileName, token]); return ( { > {children} - ) -} + ); +}; -export const useAuth = () => useContext(AuthContext) +export const useAuth = () => useContext(AuthContext); diff --git a/middlewares/auth_required.js b/middlewares/auth_required.js index 7f5e7bc..ce7374b 100644 --- a/middlewares/auth_required.js +++ b/middlewares/auth_required.js @@ -1,3 +1,17 @@ /*** * @todo Redirect the user to login page if token is not present. */ +import { useEffect } from "react"; +import { useAuth } from "../context/auth"; +import { useRouter } from "next/router"; + +export function auth_required() { + const router = useRouter(); + const { token } = useAuth(); + + useEffect(() => { + if (token === undefined || token === null) { + router.push("/login"); + } + }, [token]); +} diff --git a/middlewares/no_auth_required.js b/middlewares/no_auth_required.js index 82558d4..5fa9f21 100644 --- a/middlewares/no_auth_required.js +++ b/middlewares/no_auth_required.js @@ -1,3 +1,17 @@ /*** * @todo Redirect the user to main page if token is present. - */ \ No newline at end of file + */ +import { useRouter } from "next/router"; +import { useEffect } from "react"; +import { useAuth } from "../context/auth"; + +export function no_auth_required() { + const router = useRouter(); + const { token } = useAuth(); + + useEffect(() => { + if (token) { + router.push("/"); + } + }, [token]); +} diff --git a/package.json b/package.json index 98b7437..581fc3e 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,10 @@ "lint": "next lint" }, "dependencies": { + "@emotion/react": "^11.9.3", + "@emotion/styled": "^11.9.3", + "@mui/icons-material": "^5.8.4", + "@mui/material": "^5.8.5", "autoprefixer": "^10.2.6", "axios": "^0.21.1", "izitoast": "^1.4.0", @@ -17,6 +21,7 @@ "react": "17.0.2", "react-cookie": "^4.0.3", "react-dom": "17.0.2", + "react-toastify": "^9.0.5", "tailwindcss": "^2.2.2" }, "devDependencies": { diff --git a/pages/_app.js b/pages/_app.js index 9fc71da..1d7d5fe 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -1,14 +1,16 @@ -import '../styles/globals.css' -import { AuthProvider } from '../context/auth' -import Nav from '../components/Nav' +import "../styles/globals.css"; +import { AuthProvider } from "../context/auth"; +import Nav from "../components/Nav"; function MyApp({ Component, pageProps }) { return ( - -