From 1dbca6e087a569f2ff40e2994bf7ba3d28f07553 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 29 Sep 2024 00:50:14 +0530 Subject: [PATCH 1/3] Styling Left --- backend/middleware/validateMiddleware.js | 2 +- backend/routes/user.js | 22 +++-- frontend/src/App.jsx | 25 +++--- frontend/src/components/AddTodo.jsx | 15 +++- frontend/src/components/AppBar.jsx | 3 +- frontend/src/components/CompleteButton.jsx | 30 +++++-- frontend/src/components/DeleteButton.jsx | 4 + frontend/src/components/EditTodo.jsx | 98 ++++++++++++++++++++++ frontend/src/components/Fields.jsx | 29 ++++++- frontend/src/components/Logout.jsx | 6 ++ frontend/src/components/Todos.jsx | 68 +++++++-------- frontend/src/context.jsx | 13 +++ 12 files changed, 239 insertions(+), 76 deletions(-) create mode 100644 frontend/src/context.jsx diff --git a/backend/middleware/validateMiddleware.js b/backend/middleware/validateMiddleware.js index 0da043b..98ff203 100644 --- a/backend/middleware/validateMiddleware.js +++ b/backend/middleware/validateMiddleware.js @@ -37,7 +37,7 @@ const signinSchema = zod.object({ const todoSchema = zod.object({ title: zod.string().min(1, "Title is required").max(30), description: zod.string().optional(), - completed: zod.boolean(), + completed: zod.boolean().optional(), }); module.exports = { diff --git a/backend/routes/user.js b/backend/routes/user.js index a48e670..7acfb0c 100644 --- a/backend/routes/user.js +++ b/backend/routes/user.js @@ -29,7 +29,9 @@ router.post("/signup", validateMiddleware(userSchema), async (req, res) => { password, }); - const token = jwt.sign({ userId: newUser._id }, JWT_SECRET, {}); + const token = jwt.sign({ userId: newUser._id }, JWT_SECRET, { + expiresIn: "1h", + }); return res.status(200).json({ message: "User Created Successfully", @@ -59,7 +61,9 @@ router.post("/login", validateMiddleware(signinSchema), async (req, res) => { }); } - const token = jwt.sign({ userId: response._id }, JWT_SECRET, {}); + const token = jwt.sign({ userId: response._id }, JWT_SECRET, { + expiresIn: "1h", + }); return res.status(200).json({ message: "User Signed In Successfully", @@ -141,11 +145,12 @@ router.post( // Update Todo Route router.put( - "/updateTodo", + "/updateTodo/:id", authMiddleware, validateMiddleware(todoSchema), async (req, res) => { - const { id, title, description, completed } = req.body; + const { title, description, completed } = req.body; + const { id } = req.params; try { // Create an update object based on which fields are present @@ -170,13 +175,14 @@ router.put( ); router.put("/completeTodo/:id", authMiddleware, async (req, res) => { - const { id } = req.params; // Now getting id from the URL params + const { id } = req.params; + const { completed } = req.body; // Get the completed state from the request body try { const updatedTodo = await Todo.findByIdAndUpdate( id, - { completed: true }, - { new: true } + { completed }, // Use the completed state from the request + { new: true } // Return the updated document ); if (!updatedTodo) { @@ -185,7 +191,7 @@ router.put("/completeTodo/:id", authMiddleware, async (req, res) => { res.status(200).json(updatedTodo); } catch (error) { - res.status(500).json({ error: "Failed to complete todo" }); + res.status(500).json({ error: "Failed to update todo" }); } }); diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index dc5b560..624de73 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -6,26 +6,23 @@ import { Login } from "./components/Login"; import { AppBar } from "./components/AppBar"; import { Todos } from "./components/Todos"; import { NewHome } from "./components/NewHome"; +import { TodoRender } from "./context"; function App() { - console.log("App Rerenders"); - const token = localStorage.getItem("token"); return ( - - - } /> - } /> - } /> - {token ? ( - } /> - ) : ( - } /> - )} - } /> - + + + + } /> + } /> + } /> + : } /> + } /> + + ); } diff --git a/frontend/src/components/AddTodo.jsx b/frontend/src/components/AddTodo.jsx index 6207c22..972fa70 100644 --- a/frontend/src/components/AddTodo.jsx +++ b/frontend/src/components/AddTodo.jsx @@ -1,19 +1,26 @@ -import { useState } from "react"; +import { useState, useContext } from "react"; import { Fields } from "./Fields"; +import { TodoContext } from "../context"; export function AddTodo() { const [showFields, setShowFields] = useState(false); + const { setRender } = useContext(TodoContext); // Get setRender from context const toggleFields = () => { - setShowFields(true); // Toggle visibility of Fields + setShowFields(!showFields); + }; + + const handleTodoAdded = () => { + setShowFields(false); // Hide the form after adding a todo + setRender((prev) => !prev); // Re-fetch todos after adding a new one }; return (
- {showFields && } {/* Conditionally render Fields */} + {showFields && }
); } diff --git a/frontend/src/components/AppBar.jsx b/frontend/src/components/AppBar.jsx index 1f985f1..9fdf8f1 100644 --- a/frontend/src/components/AppBar.jsx +++ b/frontend/src/components/AppBar.jsx @@ -1,6 +1,5 @@ -import { Router, useNavigate } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; import { Logout } from "./Logout"; -import { Todos } from "./Todos"; export function AppBar() { const navigate = useNavigate(); diff --git a/frontend/src/components/CompleteButton.jsx b/frontend/src/components/CompleteButton.jsx index 35132b0..2a50b42 100644 --- a/frontend/src/components/CompleteButton.jsx +++ b/frontend/src/components/CompleteButton.jsx @@ -1,13 +1,20 @@ +// components/CompleteButton.jsx + import axios from "axios"; +import { useContext } from "react"; +import { TodoContext } from "../context"; // CompleteButton component to mark a todo as complete -export function CompleteButton({ todoId }) { +export function CompleteButton({ todoId, isCompleted }) { + // Pass isCompleted as a prop + const { render, setRender } = useContext(TodoContext); + const onClick = async () => { try { - // Send todoId in the URL as a parameter + // Send todoId in the URL and toggle the 'completed' state await axios.put( - `http://localhost:3000/user/completeTodo/${todoId}`, // Pass todoId in the URL - {}, + `http://localhost:3000/user/completeTodo/${todoId}`, + { completed: !isCompleted }, // Toggle the completed state { headers: { "Content-Type": "application/json", @@ -15,15 +22,24 @@ export function CompleteButton({ todoId }) { }, } ); - console.log("Todo Completed"); + + console.log( + isCompleted ? "Todo marked as incomplete" : "Todo marked as complete" + ); + setRender((prev) => !prev); // Re-render the todo list } catch (error) { console.log("Error While Completing Todo: " + error); } }; return ( - ); } diff --git a/frontend/src/components/DeleteButton.jsx b/frontend/src/components/DeleteButton.jsx index 224fb91..7692c95 100644 --- a/frontend/src/components/DeleteButton.jsx +++ b/frontend/src/components/DeleteButton.jsx @@ -1,6 +1,9 @@ import axios from "axios"; +import { useContext } from "react"; +import { TodoContext } from "../context"; export function DeleteButton({ todoId }) { + const { render, setRender } = useContext(TodoContext); const onDelete = async () => { try { await axios.delete(`http://localhost:3000/user/delete/${todoId}`, { @@ -8,6 +11,7 @@ export function DeleteButton({ todoId }) { Authorization: "Bearer " + localStorage.getItem("token"), }, }); + setRender((prev) => !prev); } catch (error) { console.error("Some Error Occured " + error); } diff --git a/frontend/src/components/EditTodo.jsx b/frontend/src/components/EditTodo.jsx index 8b13789..d0de0eb 100644 --- a/frontend/src/components/EditTodo.jsx +++ b/frontend/src/components/EditTodo.jsx @@ -1 +1,99 @@ +import { useState } from "react"; +import axios from "axios"; +export function EditTodo({ todo, onEditComplete }) { + const [editMode, setEditMode] = useState(false); + const [editTitle, setEditTitle] = useState(todo.title); + const [editDescription, setEditDescription] = useState(todo.description); + const [isCompleted, setIsCompleted] = useState(todo.completed); // Track completed state + + const handleEditClick = () => { + setEditMode(true); // Enable editing mode + }; + + const handleCancelClick = () => { + setEditMode(false); // Disable editing mode + setEditTitle(todo.title); // Reset title to original + setEditDescription(todo.description); // Reset description to original + setIsCompleted(todo.completed); // Reset completed state to original + }; + + const handleSaveClick = async () => { + try { + await axios.put( + `http://localhost:3000/user/updateTodo/${todo._id}`, + { + title: editTitle, + description: editDescription, + completed: isCompleted, // Include completed state in the update + }, + { + headers: { + Authorization: "Bearer " + localStorage.getItem("token"), + }, + } + ); + setEditMode(false); // Disable editing mode after save + onEditComplete(); // Trigger re-render of todos after saving + } catch (error) { + console.error("Error saving todo:", error); + } + }; + + return ( +
+
+ setEditTitle(e.target.value)} + className={`border ${ + editMode ? "border-blue-300" : "border-gray-300" + } rounded p-2 w-full mb-2`} + /> + setEditDescription(e.target.value)} + className={`border ${ + editMode ? "border-blue-300" : "border-gray-300" + } rounded p-2 w-full mb-2`} + /> + {editMode && ( // Only show the checkbox when in edit mode +
+ setIsCompleted(e.target.checked)} // Update completed state + disabled={!editMode} // Disable checkbox when not in edit mode + /> + +
+ )} + {editMode ? ( +
+ + +
+ ) : ( + + )} +
+
+ ); +} diff --git a/frontend/src/components/Fields.jsx b/frontend/src/components/Fields.jsx index 4d22a97..15bd947 100644 --- a/frontend/src/components/Fields.jsx +++ b/frontend/src/components/Fields.jsx @@ -1,13 +1,23 @@ import { useState } from "react"; import axios from "axios"; -export function Fields() { +export function Fields({ onTodoAdded }) { const [title, setTitle] = useState(""); const [description, setDescription] = useState(""); + const [error, setError] = useState(null); + const [successMessage, setSuccessMessage] = useState(null); const clickHere = async () => { + setError(null); + setSuccessMessage(null); + + if (!title || !description) { + setError("Please fill in both fields."); + return; + } + try { - const response = await axios.post( + await axios.post( "http://localhost:3000/user/addTodo", { title: title, @@ -20,8 +30,17 @@ export function Fields() { }, } ); + + // Clear the fields after successful submission + setTitle(""); + setDescription(""); + setSuccessMessage("Todo added successfully!"); + + // Notify parent to refresh the list and hide form + onTodoAdded(); } catch (error) { console.error(error); + setError("Error adding todo, please try again."); } }; @@ -39,7 +58,11 @@ export function Fields() { value={description} onChange={(e) => setDescription(e.target.value)} /> - + + + {/* Show success or error messages */} + {error &&

{error}

} + {successMessage &&

{successMessage}

} ); } diff --git a/frontend/src/components/Logout.jsx b/frontend/src/components/Logout.jsx index ef6dbd3..9cfc4ce 100644 --- a/frontend/src/components/Logout.jsx +++ b/frontend/src/components/Logout.jsx @@ -1,11 +1,17 @@ +import { useContext } from "react"; import { useNavigate } from "react-router-dom"; +import { TodoContext } from "../context"; export function Logout() { const navigate = useNavigate(); + const { setRender } = useContext(TodoContext); + const onLogout = () => { localStorage.removeItem("token"); + setRender((prev) => !prev); navigate("/"); }; + return (
diff --git a/frontend/src/components/Todos.jsx b/frontend/src/components/Todos.jsx index 526169b..8bd90bc 100644 --- a/frontend/src/components/Todos.jsx +++ b/frontend/src/components/Todos.jsx @@ -1,66 +1,60 @@ -import { useEffect, useState } from "react"; -import axios from "axios"; +import { useContext, useEffect, useState } from "react"; +import { TodoContext } from "../context"; import { CompleteButton } from "./CompleteButton"; import { DeleteButton } from "./DeleteButton"; +import axios from "axios"; import { AddTodo } from "./AddTodo"; +import { EditTodo } from "./EditTodo"; // Import the new EditTodos component + export function Todos() { + const { render, setRender } = useContext(TodoContext); const [todos, setTodos] = useState([]); useEffect(() => { const fetchTodos = async () => { try { - const result = await axios.get("http://localhost:3000/user/todos", { + const response = await axios.get("http://localhost:3000/user/todos", { headers: { Authorization: "Bearer " + localStorage.getItem("token"), }, }); - - setTodos(result.data.todos); + setTodos(response.data.todos); } catch (error) { console.error("Error fetching todos:", error); } }; - fetchTodos(); - }, [todos]); // Run only once on mount + }, [render]); - const addTodo = (newTodo) => { - setTodos((prevTodos) => [...prevTodos, newTodo]); // Update state with the new todo + const handleEditComplete = () => { + setRender((prev) => !prev); // Trigger re-fetching todos on edit completion }; return ( -
-
-

- Your Todos -

-
    - {todos.map((todo) => ( -
  • -
    -
    -

    - {todo.title} -

    -

    {todo.description}

    -
    - - -
    -

    +

    Your Todos

    +
    + {todos.map((todo) => ( +
    +
    + +
    {todo.completed ? "Completed" : "Not Completed"} -

    -
  • - ))} -
+
+ + +
+
+ ))} - + ); } diff --git a/frontend/src/context.jsx b/frontend/src/context.jsx new file mode 100644 index 0000000..afcadb8 --- /dev/null +++ b/frontend/src/context.jsx @@ -0,0 +1,13 @@ +import { createContext, useState } from "react"; + +export const TodoContext = createContext(); + +export function TodoRender({ children }) { + const [render, setRender] = useState(false); + + return ( + + {children} + + ); +} From 833ba4e26d458a9beb63651dd88cb965c98d1be9 Mon Sep 17 00:00:00 2001 From: RashmitTopG Date: Mon, 27 Jan 2025 22:04:56 +0530 Subject: [PATCH 2/3] Added Workflow --- .github/workflows/build.yml | 23 ++ backend/config.js | 6 +- backend/db.js | 58 +-- backend/index.js | 36 +- backend/middleware/authMiddleware.js | 86 ++--- backend/middleware/validateMiddleware.js | 96 ++--- backend/routes/user.js | 430 ++++++++++----------- frontend/src/components/AddTodo.jsx | 61 +-- frontend/src/components/AppBar.jsx | 81 ++-- frontend/src/components/CompleteButton.jsx | 87 ++--- frontend/src/components/DeleteButton.jsx | 57 +-- frontend/src/components/EditTodo.jsx | 201 +++++----- frontend/src/components/Fields.jsx | 146 +++---- frontend/src/components/Landing.jsx | 48 +-- frontend/src/components/Login.jsx | 196 +++++----- frontend/src/components/Logout.jsx | 45 ++- frontend/src/components/NewHome.jsx | 14 +- frontend/src/components/Signup.jsx | 200 +++++----- frontend/src/components/Todos.jsx | 124 +++--- frontend/src/context.jsx | 26 +- 20 files changed, 1047 insertions(+), 974 deletions(-) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..74f1470 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,23 @@ +name: Build on PR + +on: + pull_request: + branches: + - '*' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' + + - name: Install Dependencies + run: npm install + + - name: Run Build + run: npm run build diff --git a/backend/config.js b/backend/config.js index 45fac5b..04163bf 100644 --- a/backend/config.js +++ b/backend/config.js @@ -1,3 +1,3 @@ -const JWT_SECRET = "IAMGAYLOL"; - -module.exports = JWT_SECRET; +const JWT_SECRET = "IAMGAYLOL"; + +module.exports = JWT_SECRET; diff --git a/backend/db.js b/backend/db.js index 1763e05..7c29252 100644 --- a/backend/db.js +++ b/backend/db.js @@ -1,29 +1,29 @@ -const mongoose = require("mongoose"); -mongoose.connect("mongodb://localhost:27017/basicTodoApp"); - -const UserSchema = mongoose.Schema({ - firstName: String, - lastName: String, - username: String, - password: String, - todos: [ - { - type: mongoose.Schema.Types.ObjectId, - ref: "Todo", - }, - ], -}); - -const TodoSchema = mongoose.Schema({ - title: String, - description: String, - completed: Boolean, -}); - -const User = mongoose.model("User", UserSchema); -const Todo = mongoose.model("Todo", TodoSchema); - -module.exports = { - User, - Todo, -}; +const mongoose = require("mongoose"); +mongoose.connect("mongodb://localhost:27017/basicTodoApp"); + +const UserSchema = mongoose.Schema({ + firstName: String, + lastName: String, + username: String, + password: String, + todos: [ + { + type: mongoose.Schema.Types.ObjectId, + ref: "Todo", + }, + ], +}); + +const TodoSchema = mongoose.Schema({ + title: String, + description: String, + completed: Boolean, +}); + +const User = mongoose.model("User", UserSchema); +const Todo = mongoose.model("Todo", TodoSchema); + +module.exports = { + User, + Todo, +}; diff --git a/backend/index.js b/backend/index.js index 254aaa9..fbeb24c 100644 --- a/backend/index.js +++ b/backend/index.js @@ -1,18 +1,18 @@ -const express = require("express"); -const PORT = 3000; -const bodyParser = require("body-parser"); -const userRouter = require("./routes/user"); -const cors = require("cors"); - -const app = express(); -app.use(cors()); -app.use(bodyParser.json()); -app.use("/user", userRouter); - -app.get("/", (req, res) => { - res.send("Hello World! Welcome to Todo"); -}); - -app.listen(PORT, () => { - console.log(`Server is running on port ${PORT}`); -}); +const express = require("express"); +const PORT = 3000; +const bodyParser = require("body-parser"); +const userRouter = require("./routes/user"); +const cors = require("cors"); + +const app = express(); +app.use(cors()); +app.use(bodyParser.json()); +app.use("/user", userRouter); + +app.get("/", (req, res) => { + res.send("Hello World! Welcome to Todo"); +}); + +app.listen(PORT, () => { + console.log(`Server is running on port ${PORT}`); +}); diff --git a/backend/middleware/authMiddleware.js b/backend/middleware/authMiddleware.js index 3c432c4..18d6f4d 100644 --- a/backend/middleware/authMiddleware.js +++ b/backend/middleware/authMiddleware.js @@ -1,43 +1,43 @@ -const jwt = require("jsonwebtoken"); -const JWT_SECRET = require("../config"); - -const authMiddleware = (req, res, next) => { - const authHeader = req.headers.authorization; - - if (!authHeader) { - return res.status(401).json({ - message: "No Authorization Header Found", - }); - } - - const words = authHeader.split(" "); - - if (words[0] !== "Bearer" || words.length !== 2) { - return res.status(401).json({ - message: "Invalid Token Format", - }); - } - - const token = words[1]; - - try { - const decoded = jwt.verify(token, JWT_SECRET); - - if (decoded.userId) { - req.userId = decoded.userId; - next(); - } else { - res.status(401).json({ - message: "Invalid Token", - }); - } - } catch (error) { - console.error("The Error is " + error); - res.status(500).json({ - message: "Some Error Occurred", - error: error.message, - }); - } -}; - -module.exports = authMiddleware; // Ensure this is correct +const jwt = require("jsonwebtoken"); +const JWT_SECRET = require("../config"); + +const authMiddleware = (req, res, next) => { + const authHeader = req.headers.authorization; + + if (!authHeader) { + return res.status(401).json({ + message: "No Authorization Header Found", + }); + } + + const words = authHeader.split(" "); + + if (words[0] !== "Bearer" || words.length !== 2) { + return res.status(401).json({ + message: "Invalid Token Format", + }); + } + + const token = words[1]; + + try { + const decoded = jwt.verify(token, JWT_SECRET); + + if (decoded.userId) { + req.userId = decoded.userId; + next(); + } else { + res.status(401).json({ + message: "Invalid Token", + }); + } + } catch (error) { + console.error("The Error is " + error); + res.status(500).json({ + message: "Some Error Occurred", + error: error.message, + }); + } +}; + +module.exports = authMiddleware; // Ensure this is correct diff --git a/backend/middleware/validateMiddleware.js b/backend/middleware/validateMiddleware.js index 98ff203..3854d39 100644 --- a/backend/middleware/validateMiddleware.js +++ b/backend/middleware/validateMiddleware.js @@ -1,48 +1,48 @@ -const zod = require("zod"); -const { default: errorMap } = require("zod/locales/en.js"); - -const validateMiddleware = (schema) => (req, res, next) => { - try { - const result = schema.safeParse(req.body); - if (!result.success) { - return res.status(400).json({ - message: "Validation Error", - errors: result.error.errors.map((err) => ({ - path: err.path.join("."), // Path of the field with the error - message: err.message, // Error message - })), - }); - } - next(); - } catch (error) { - return res.status(500).json({ - message: "Internal Server Error", - error: error.message, - }); - } -}; - -const userSchema = zod.object({ - firstName: zod.string().min(1).max(30), // Should not be empty - lastName: zod.string().min(1).max(30), // Should not be empty - username: zod.string().email().min(1), // Should not be empty and must be a valid email - password: zod.string().min(8).max(20), // Length constraints -}); - -const signinSchema = zod.object({ - username: zod.string().email(), - password: zod.string().min(8).max(20), -}); - -const todoSchema = zod.object({ - title: zod.string().min(1, "Title is required").max(30), - description: zod.string().optional(), - completed: zod.boolean().optional(), -}); - -module.exports = { - userSchema, - todoSchema, - signinSchema, - validateMiddleware, -}; +const zod = require("zod"); +const { default: errorMap } = require("zod/locales/en.js"); + +const validateMiddleware = (schema) => (req, res, next) => { + try { + const result = schema.safeParse(req.body); + if (!result.success) { + return res.status(400).json({ + message: "Validation Error", + errors: result.error.errors.map((err) => ({ + path: err.path.join("."), // Path of the field with the error + message: err.message, // Error message + })), + }); + } + next(); + } catch (error) { + return res.status(500).json({ + message: "Internal Server Error", + error: error.message, + }); + } +}; + +const userSchema = zod.object({ + firstName: zod.string().min(1).max(30), // Should not be empty + lastName: zod.string().min(1).max(30), // Should not be empty + username: zod.string().email().min(1), // Should not be empty and must be a valid email + password: zod.string().min(8).max(20), // Length constraints +}); + +const signinSchema = zod.object({ + username: zod.string().email(), + password: zod.string().min(8).max(20), +}); + +const todoSchema = zod.object({ + title: zod.string().min(1, "Title is required").max(30), + description: zod.string().optional(), + completed: zod.boolean().optional(), +}); + +module.exports = { + userSchema, + todoSchema, + signinSchema, + validateMiddleware, +}; diff --git a/backend/routes/user.js b/backend/routes/user.js index 7acfb0c..6b36b6e 100644 --- a/backend/routes/user.js +++ b/backend/routes/user.js @@ -1,215 +1,215 @@ -const { Router } = require("express"); -const router = Router(); -const { - validateMiddleware, - userSchema, - todoSchema, - signinSchema, -} = require("../middleware/validateMiddleware"); -const authMiddleware = require("../middleware/authMiddleware"); -const jwt = require("jsonwebtoken"); -const { User, Todo } = require("../db"); -const JWT_SECRET = require("../config"); - -// Signup Route -router.post("/signup", validateMiddleware(userSchema), async (req, res) => { - const { firstName, lastName, username, password } = req.body; - try { - const response = await User.findOne({ username }); - if (response) { - return res.status(400).json({ - message: "User Already Exists", - }); - } - - const newUser = await User.create({ - firstName, - lastName, - username, - password, - }); - - const token = jwt.sign({ userId: newUser._id }, JWT_SECRET, { - expiresIn: "1h", - }); - - return res.status(200).json({ - message: "User Created Successfully", - success: true, - token: token, - }); - } catch (error) { - console.error("Error during signup:", error); // Log the error details - return res.status(500).json({ - message: "Some Error Occurred", - success: false, - error: error.message, // Include error message in response for debugging - }); - } -}); - -// Signin Route -router.post("/login", validateMiddleware(signinSchema), async (req, res) => { - const { username, password } = req.body; - - try { - const response = await User.findOne({ username, password }); - if (!response) { - return res.status(400).json({ - message: "No User Found with the credentials", - success: false, - }); - } - - const token = jwt.sign({ userId: response._id }, JWT_SECRET, { - expiresIn: "1h", - }); - - return res.status(200).json({ - message: "User Signed In Successfully", - success: true, - token: token, - }); - } catch (error) { - return res.status(500).json({ - message: "Some Error Occurred", - }); - } -}); - -// Get Todos Route -router.get("/todos", authMiddleware, async (req, res) => { - const userId = req.userId; - - try { - const user = await User.findById(userId).populate("todos"); // Use userId here - - if (!user) { - return res.status(400).json({ - message: "No User found with todos", - }); - } - - return res.status(200).json({ - todos: user.todos, - }); - } catch (error) { - console.error("The error is " + error); - return res.status(500).json({ - message: "Some Error Occurred", - error: error.message, - }); - } -}); - -// Add Todo Route -router.post( - "/addTodo", - authMiddleware, - validateMiddleware(todoSchema), - async (req, res) => { - const userId = req.userId; // Corrected to match the field set by authMiddleware - const { title, description, completed } = req.body; - - try { - const newTodo = await Todo.create({ - title, - description, - completed, - }); - - const user = await User.findByIdAndUpdate( - userId, - { $push: { todos: newTodo._id } }, - { new: true } - ); - - if (!user) { - return res.status(404).json({ - message: "User not found", - }); - } - - // Return a success response - return res.status(201).json({ - message: "Todo added successfully", - todo: newTodo, - }); - } catch (error) { - return res.status(500).json({ - message: "Some Error Occurred", - }); - } - } -); - -// Update Todo Route -router.put( - "/updateTodo/:id", - authMiddleware, - validateMiddleware(todoSchema), - async (req, res) => { - const { title, description, completed } = req.body; - const { id } = req.params; - - try { - // Create an update object based on which fields are present - const updateData = {}; - if (title !== undefined) updateData.title = title; - if (description !== undefined) updateData.description = description; - if (completed !== undefined) updateData.completed = completed; - - const updatedTodo = await Todo.findByIdAndUpdate(id, updateData, { - new: true, - }); - - if (!updatedTodo) { - return res.status(404).json({ error: "Todo not found" }); - } - - res.status(200).json(updatedTodo); - } catch (error) { - res.status(500).json({ error: "Failed to update todo" }); - } - } -); - -router.put("/completeTodo/:id", authMiddleware, async (req, res) => { - const { id } = req.params; - const { completed } = req.body; // Get the completed state from the request body - - try { - const updatedTodo = await Todo.findByIdAndUpdate( - id, - { completed }, // Use the completed state from the request - { new: true } // Return the updated document - ); - - if (!updatedTodo) { - return res.status(404).json({ error: "Todo not found" }); - } - - res.status(200).json(updatedTodo); - } catch (error) { - res.status(500).json({ error: "Failed to update todo" }); - } -}); - -// Delete Todo Route -router.delete("/delete/:id", authMiddleware, async (req, res) => { - const { id } = req.params; // Extract the todo id from the request body - - try { - const deletedTodo = await Todo.findByIdAndDelete(id); // Find and delete the todo by id - - if (!deletedTodo) { - return res.status(404).json({ error: "Todo not found" }); - } - - res.status(200).json({ message: "Todo deleted successfully", deletedTodo }); - } catch (error) { - res.status(500).json({ error: "Failed to delete todo" }); - } -}); - -module.exports = router; +const { Router } = require("express"); +const router = Router(); +const { + validateMiddleware, + userSchema, + todoSchema, + signinSchema, +} = require("../middleware/validateMiddleware"); +const authMiddleware = require("../middleware/authMiddleware"); +const jwt = require("jsonwebtoken"); +const { User, Todo } = require("../db"); +const JWT_SECRET = require("../config"); + +// Signup Route +router.post("/signup", validateMiddleware(userSchema), async (req, res) => { + const { firstName, lastName, username, password } = req.body; + try { + const response = await User.findOne({ username }); + if (response) { + return res.status(400).json({ + message: "User Already Exists", + }); + } + + const newUser = await User.create({ + firstName, + lastName, + username, + password, + }); + + const token = jwt.sign({ userId: newUser._id }, JWT_SECRET, { + expiresIn: "1h", + }); + + return res.status(200).json({ + message: "User Created Successfully", + success: true, + token: token, + }); + } catch (error) { + console.error("Error during signup:", error); // Log the error details + return res.status(500).json({ + message: "Some Error Occurred", + success: false, + error: error.message, // Include error message in response for debugging + }); + } +}); + +// Signin Route +router.post("/login", validateMiddleware(signinSchema), async (req, res) => { + const { username, password } = req.body; + + try { + const response = await User.findOne({ username, password }); + if (!response) { + return res.status(400).json({ + message: "No User Found with the credentials", + success: false, + }); + } + + const token = jwt.sign({ userId: response._id }, JWT_SECRET, { + expiresIn: "1h", + }); + + return res.status(200).json({ + message: "User Signed In Successfully", + success: true, + token: token, + }); + } catch (error) { + return res.status(500).json({ + message: "Some Error Occurred", + }); + } +}); + +// Get Todos Route +router.get("/todos", authMiddleware, async (req, res) => { + const userId = req.userId; + + try { + const user = await User.findById(userId).populate("todos"); // Use userId here + + if (!user) { + return res.status(400).json({ + message: "No User found with todos", + }); + } + + return res.status(200).json({ + todos: user.todos, + }); + } catch (error) { + console.error("The error is " + error); + return res.status(500).json({ + message: "Some Error Occurred", + error: error.message, + }); + } +}); + +// Add Todo Route +router.post( + "/addTodo", + authMiddleware, + validateMiddleware(todoSchema), + async (req, res) => { + const userId = req.userId; // Corrected to match the field set by authMiddleware + const { title, description, completed } = req.body; + + try { + const newTodo = await Todo.create({ + title, + description, + completed, + }); + + const user = await User.findByIdAndUpdate( + userId, + { $push: { todos: newTodo._id } }, + { new: true } + ); + + if (!user) { + return res.status(404).json({ + message: "User not found", + }); + } + + // Return a success response + return res.status(201).json({ + message: "Todo added successfully", + todo: newTodo, + }); + } catch (error) { + return res.status(500).json({ + message: "Some Error Occurred", + }); + } + } +); + +// Update Todo Route +router.put( + "/updateTodo/:id", + authMiddleware, + validateMiddleware(todoSchema), + async (req, res) => { + const { title, description, completed } = req.body; + const { id } = req.params; + + try { + // Create an update object based on which fields are present + const updateData = {}; + if (title !== undefined) updateData.title = title; + if (description !== undefined) updateData.description = description; + if (completed !== undefined) updateData.completed = completed; + + const updatedTodo = await Todo.findByIdAndUpdate(id, updateData, { + new: true, + }); + + if (!updatedTodo) { + return res.status(404).json({ error: "Todo not found" }); + } + + res.status(200).json(updatedTodo); + } catch (error) { + res.status(500).json({ error: "Failed to update todo" }); + } + } +); + +router.put("/completeTodo/:id", authMiddleware, async (req, res) => { + const { id } = req.params; + const { completed } = req.body; // Get the completed state from the request body + + try { + const updatedTodo = await Todo.findByIdAndUpdate( + id, + { completed }, // Use the completed state from the request + { new: true } // Return the updated document + ); + + if (!updatedTodo) { + return res.status(404).json({ error: "Todo not found" }); + } + + res.status(200).json(updatedTodo); + } catch (error) { + res.status(500).json({ error: "Failed to update todo" }); + } +}); + +// Delete Todo Route +router.delete("/delete/:id", authMiddleware, async (req, res) => { + const { id } = req.params; // Extract the todo id from the request body + + try { + const deletedTodo = await Todo.findByIdAndDelete(id); // Find and delete the todo by id + + if (!deletedTodo) { + return res.status(404).json({ error: "Todo not found" }); + } + + res.status(200).json({ message: "Todo deleted successfully", deletedTodo }); + } catch (error) { + res.status(500).json({ error: "Failed to delete todo" }); + } +}); + +module.exports = router; diff --git a/frontend/src/components/AddTodo.jsx b/frontend/src/components/AddTodo.jsx index 972fa70..dbfb521 100644 --- a/frontend/src/components/AddTodo.jsx +++ b/frontend/src/components/AddTodo.jsx @@ -1,26 +1,35 @@ -import { useState, useContext } from "react"; -import { Fields } from "./Fields"; -import { TodoContext } from "../context"; - -export function AddTodo() { - const [showFields, setShowFields] = useState(false); - const { setRender } = useContext(TodoContext); // Get setRender from context - - const toggleFields = () => { - setShowFields(!showFields); - }; - - const handleTodoAdded = () => { - setShowFields(false); // Hide the form after adding a todo - setRender((prev) => !prev); // Re-fetch todos after adding a new one - }; - - return ( -
- - {showFields && } -
- ); -} +import { useState, useContext } from "react"; +import { Fields } from "./Fields"; +import { TodoContext } from "../context"; + +export function AddTodo() { + const [showFields, setShowFields] = useState(false); + const { setRender } = useContext(TodoContext); // Get setRender from context + + const toggleFields = () => { + setShowFields(!showFields); + }; + + const handleTodoAdded = () => { + setShowFields(false); // Hide the form after adding a todo + setRender((prev) => !prev); // Re-fetch todos after adding a new one + }; + + return ( +
+ + {showFields && ( +
+ +
+ )} +
+ ); +} diff --git a/frontend/src/components/AppBar.jsx b/frontend/src/components/AppBar.jsx index 9fdf8f1..6ac18e7 100644 --- a/frontend/src/components/AppBar.jsx +++ b/frontend/src/components/AppBar.jsx @@ -1,32 +1,49 @@ -import { useNavigate } from "react-router-dom"; -import { Logout } from "./Logout"; - -export function AppBar() { - const navigate = useNavigate(); - const token = localStorage.getItem("token"); - - return ( -
- - {token ? ( - - ) : null} - {!token ? ( -
- - -
- ) : null} - - {token ? : null} -
- ); -} +import { useNavigate } from "react-router-dom"; +import { Logout } from "./Logout"; + +export function AppBar() { + const navigate = useNavigate(); + const token = localStorage.getItem("token"); + + return ( +
+
+ + {token ? ( + + ) : null} +
+ + {!token ? ( +
+ + +
+ ) : null} + + {token ? : null} +
+ ); +} diff --git a/frontend/src/components/CompleteButton.jsx b/frontend/src/components/CompleteButton.jsx index 2a50b42..3d512ee 100644 --- a/frontend/src/components/CompleteButton.jsx +++ b/frontend/src/components/CompleteButton.jsx @@ -1,45 +1,42 @@ -// components/CompleteButton.jsx - -import axios from "axios"; -import { useContext } from "react"; -import { TodoContext } from "../context"; - -// CompleteButton component to mark a todo as complete -export function CompleteButton({ todoId, isCompleted }) { - // Pass isCompleted as a prop - const { render, setRender } = useContext(TodoContext); - - const onClick = async () => { - try { - // Send todoId in the URL and toggle the 'completed' state - await axios.put( - `http://localhost:3000/user/completeTodo/${todoId}`, - { completed: !isCompleted }, // Toggle the completed state - { - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + localStorage.getItem("token"), - }, - } - ); - - console.log( - isCompleted ? "Todo marked as incomplete" : "Todo marked as complete" - ); - setRender((prev) => !prev); // Re-render the todo list - } catch (error) { - console.log("Error While Completing Todo: " + error); - } - }; - - return ( - - ); -} +import axios from "axios"; +import { useContext } from "react"; +import { TodoContext } from "../context"; + +// CompleteButton component to mark a todo as complete +export function CompleteButton({ todoId, isCompleted }) { + const { render, setRender } = useContext(TodoContext); + + const onClick = async () => { + try { + await axios.put( + `http://localhost:3000/user/completeTodo/${todoId}`, + { completed: !isCompleted }, + { + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + localStorage.getItem("token"), + }, + } + ); + console.log( + isCompleted ? "Todo marked as incomplete" : "Todo marked as complete" + ); + setRender((prev) => !prev); + } catch (error) { + console.log("Error While Completing Todo: " + error); + } + }; + + return ( + + ); +} diff --git a/frontend/src/components/DeleteButton.jsx b/frontend/src/components/DeleteButton.jsx index 7692c95..39cd61e 100644 --- a/frontend/src/components/DeleteButton.jsx +++ b/frontend/src/components/DeleteButton.jsx @@ -1,26 +1,31 @@ -import axios from "axios"; -import { useContext } from "react"; -import { TodoContext } from "../context"; - -export function DeleteButton({ todoId }) { - const { render, setRender } = useContext(TodoContext); - const onDelete = async () => { - try { - await axios.delete(`http://localhost:3000/user/delete/${todoId}`, { - headers: { - Authorization: "Bearer " + localStorage.getItem("token"), - }, - }); - setRender((prev) => !prev); - } catch (error) { - console.error("Some Error Occured " + error); - } - }; - return ( -
- -
- ); -} +import axios from "axios"; +import { useContext } from "react"; +import { TodoContext } from "../context"; + +export function DeleteButton({ todoId }) { + const { render, setRender } = useContext(TodoContext); + + const onDelete = async () => { + try { + await axios.delete(`http://localhost:3000/user/delete/${todoId}`, { + headers: { + Authorization: "Bearer " + localStorage.getItem("token"), + }, + }); + setRender((prev) => !prev); + } catch (error) { + console.error("Some Error Occurred " + error); + } + }; + + return ( +
+ +
+ ); +} diff --git a/frontend/src/components/EditTodo.jsx b/frontend/src/components/EditTodo.jsx index d0de0eb..d12e7f9 100644 --- a/frontend/src/components/EditTodo.jsx +++ b/frontend/src/components/EditTodo.jsx @@ -1,99 +1,102 @@ -import { useState } from "react"; -import axios from "axios"; - -export function EditTodo({ todo, onEditComplete }) { - const [editMode, setEditMode] = useState(false); - const [editTitle, setEditTitle] = useState(todo.title); - const [editDescription, setEditDescription] = useState(todo.description); - const [isCompleted, setIsCompleted] = useState(todo.completed); // Track completed state - - const handleEditClick = () => { - setEditMode(true); // Enable editing mode - }; - - const handleCancelClick = () => { - setEditMode(false); // Disable editing mode - setEditTitle(todo.title); // Reset title to original - setEditDescription(todo.description); // Reset description to original - setIsCompleted(todo.completed); // Reset completed state to original - }; - - const handleSaveClick = async () => { - try { - await axios.put( - `http://localhost:3000/user/updateTodo/${todo._id}`, - { - title: editTitle, - description: editDescription, - completed: isCompleted, // Include completed state in the update - }, - { - headers: { - Authorization: "Bearer " + localStorage.getItem("token"), - }, - } - ); - setEditMode(false); // Disable editing mode after save - onEditComplete(); // Trigger re-render of todos after saving - } catch (error) { - console.error("Error saving todo:", error); - } - }; - - return ( -
-
- setEditTitle(e.target.value)} - className={`border ${ - editMode ? "border-blue-300" : "border-gray-300" - } rounded p-2 w-full mb-2`} - /> - setEditDescription(e.target.value)} - className={`border ${ - editMode ? "border-blue-300" : "border-gray-300" - } rounded p-2 w-full mb-2`} - /> - {editMode && ( // Only show the checkbox when in edit mode -
- setIsCompleted(e.target.checked)} // Update completed state - disabled={!editMode} // Disable checkbox when not in edit mode - /> - -
- )} - {editMode ? ( -
- - -
- ) : ( - - )} -
-
- ); -} +import { useState } from "react"; +import axios from "axios"; + +export function EditTodo({ todo, onEditComplete }) { + const [editMode, setEditMode] = useState(false); + const [editTitle, setEditTitle] = useState(todo.title); + const [editDescription, setEditDescription] = useState(todo.description); + const [isCompleted, setIsCompleted] = useState(todo.completed); // Track completed state + + const handleEditClick = () => { + setEditMode(true); // Enable editing mode + }; + + const handleCancelClick = () => { + setEditMode(false); // Disable editing mode + setEditTitle(todo.title); // Reset title to original + setEditDescription(todo.description); // Reset description to original + setIsCompleted(todo.completed); // Reset completed state to original + }; + + const handleSaveClick = async () => { + try { + await axios.put( + `http://localhost:3000/user/updateTodo/${todo._id}`, + { + title: editTitle, + description: editDescription, + completed: isCompleted, // Include completed state in the update + }, + { + headers: { + Authorization: "Bearer " + localStorage.getItem("token"), + }, + } + ); + setEditMode(false); // Disable editing mode after save + onEditComplete(); // Trigger re-render of todos after saving + } catch (error) { + console.error("Error saving todo:", error); + } + }; + + return ( +
+
+ setEditTitle(e.target.value)} + className={`border ${ + editMode ? "border-blue-300" : "border-gray-300" + } rounded p-3 w-full mb-3 transition duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500`} + /> + setEditDescription(e.target.value)} + className={`border ${ + editMode ? "border-blue-300" : "border-gray-300" + } rounded p-3 w-full mb-3 transition duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500`} + /> + {editMode && ( // Only show the checkbox when in edit mode +
+ setIsCompleted(e.target.checked)} // Update completed state + disabled={!editMode} // Disable checkbox when not in edit mode + className="mr-2 h-5 w-5 text-blue-600 border-gray-300 rounded focus:ring-blue-500" + /> + +
+ )} +
+ {editMode ? ( + <> + + + + ) : ( + + )} +
+
+
+ ); +} diff --git a/frontend/src/components/Fields.jsx b/frontend/src/components/Fields.jsx index 15bd947..aa9495f 100644 --- a/frontend/src/components/Fields.jsx +++ b/frontend/src/components/Fields.jsx @@ -1,68 +1,78 @@ -import { useState } from "react"; -import axios from "axios"; - -export function Fields({ onTodoAdded }) { - const [title, setTitle] = useState(""); - const [description, setDescription] = useState(""); - const [error, setError] = useState(null); - const [successMessage, setSuccessMessage] = useState(null); - - const clickHere = async () => { - setError(null); - setSuccessMessage(null); - - if (!title || !description) { - setError("Please fill in both fields."); - return; - } - - try { - await axios.post( - "http://localhost:3000/user/addTodo", - { - title: title, - description: description, - completed: false, - }, - { - headers: { - Authorization: "Bearer " + localStorage.getItem("token"), - }, - } - ); - - // Clear the fields after successful submission - setTitle(""); - setDescription(""); - setSuccessMessage("Todo added successfully!"); - - // Notify parent to refresh the list and hide form - onTodoAdded(); - } catch (error) { - console.error(error); - setError("Error adding todo, please try again."); - } - }; - - return ( -
- setTitle(e.target.value)} - /> - setDescription(e.target.value)} - /> - - - {/* Show success or error messages */} - {error &&

{error}

} - {successMessage &&

{successMessage}

} -
- ); -} +import { useState } from "react"; +import axios from "axios"; + +export function Fields({ onTodoAdded }) { + const [title, setTitle] = useState(""); + const [description, setDescription] = useState(""); + const [error, setError] = useState(null); + const [successMessage, setSuccessMessage] = useState(null); + + const clickHere = async () => { + setError(null); + setSuccessMessage(null); + + if (!title || !description) { + setError("Please fill in both fields."); + return; + } + + try { + await axios.post( + "http://localhost:3000/user/addTodo", + { + title: title, + description: description, + completed: false, + }, + { + headers: { + Authorization: "Bearer " + localStorage.getItem("token"), + }, + } + ); + + // Clear the fields after successful submission + setTitle(""); + setDescription(""); + setSuccessMessage("Todo added successfully!"); + + // Notify parent to refresh the list and hide form + onTodoAdded(); + } catch (error) { + console.error(error); + setError("Error adding todo, please try again."); + } + }; + + return ( +
+

Add a New Todo

+ setTitle(e.target.value)} + className="border border-gray-300 rounded p-3 w-full mb-3 focus:outline-none focus:ring-2 focus:ring-blue-500" + /> + setDescription(e.target.value)} + className="border border-gray-300 rounded p-3 w-full mb-4 focus:outline-none focus:ring-2 focus:ring-blue-500" + /> + + + {/* Show success or error messages */} + {error &&

{error}

} + {successMessage && ( +

{successMessage}

+ )} +
+ ); +} diff --git a/frontend/src/components/Landing.jsx b/frontend/src/components/Landing.jsx index 0bcb458..952cf74 100644 --- a/frontend/src/components/Landing.jsx +++ b/frontend/src/components/Landing.jsx @@ -1,24 +1,24 @@ -import { useNavigate } from "react-router-dom"; - -export function Landing() { - const navigate = useNavigate(); - - return ( -
-
-

- Welcome to Our App -

-

- The best place to manage your tasks and improve your productivity. -

- -
-
- ); -} +import { useNavigate } from "react-router-dom"; + +export function Landing() { + const navigate = useNavigate(); + + return ( +
+
+

+ Welcome to TaskNest +

+

+ The best place to manage your tasks and improve your productivity. +

+ +
+
+ ); +} diff --git a/frontend/src/components/Login.jsx b/frontend/src/components/Login.jsx index a96f57d..da9b021 100644 --- a/frontend/src/components/Login.jsx +++ b/frontend/src/components/Login.jsx @@ -1,98 +1,98 @@ -import { useState } from "react"; -import { useNavigate } from "react-router-dom"; -import axios from "axios"; - -export function Login() { - const [username, setUsername] = useState(""); - const [password, setPassword] = useState(""); - const [error, setError] = useState(""); - const navigate = useNavigate(); - - const handleSubmit = async (e) => { - e.preventDefault(); - console.log(username); - console.log(password); - - try { - const result = await axios.post("http://localhost:3000/user/login", { - username, - password, - }); - - console.log(result); - - if (result.data.success) { - localStorage.setItem("token", result.data.token); - window.alert(result.data.message); - navigate("/todos"); - // Change Later - } else { - setError(result.data.message); - } - } catch (error) { - // Safely access errors array, if it exists - const errors = error.response?.data?.errors; - - if (Array.isArray(errors) && errors.length > 0) { - const [{ message }] = errors; // Destructure first error message - window.alert(message || "Login failed"); - setError(message || "An error occurred"); - } else { - const fallbackMessage = - error.response?.data?.message || "An unknown error occurred"; - window.alert(fallbackMessage); - setError(fallbackMessage); - } - - console.error("Error during login:", error.response?.data); - } finally { - setUsername(""); - setPassword(""); - } - }; - - return ( -
-
-

Log In

- {error &&
{error}
} -
- setUsername(e.target.value)} - className="w-full p-3 border border-gray-300 rounded-md focus:outline-none focus:ring focus:ring-blue-500" - /> -
-
- setPassword(e.target.value)} - className="w-full p-3 border border-gray-300 rounded-md focus:outline-none focus:ring focus:ring-blue-500" - /> -
- -
- Don't Have an Account?{" "} - navigate("/signup")} - > - Sign Up - -
-
-
- ); -} +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import axios from "axios"; + +export function Login() { + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(""); + const navigate = useNavigate(); + + const handleSubmit = async (e) => { + e.preventDefault(); + console.log(username); + console.log(password); + + try { + const result = await axios.post("http://localhost:3000/user/login", { + username, + password, + }); + + console.log(result); + + if (result.data.success) { + localStorage.setItem("token", result.data.token); + window.alert(result.data.message); + navigate("/todos"); + // Change Later + } else { + setError(result.data.message); + } + } catch (error) { + // Safely access errors array, if it exists + const errors = error.response?.data?.errors; + + if (Array.isArray(errors) && errors.length > 0) { + const [{ message }] = errors; + window.alert(message || "Login failed"); + setError(message || "An error occurred"); + } else { + const fallbackMessage = + error.response?.data?.message || "An unknown error occurred"; + window.alert(fallbackMessage); + setError(fallbackMessage); + } + + console.error("Error during login:", error.response?.data); + } finally { + setUsername(""); + setPassword(""); + } + }; + + return ( +
+
+

Log In

+ {error &&
{error}
} +
+ setUsername(e.target.value)} + className="w-full p-3 border border-gray-300 rounded-md focus:outline-none focus:ring focus:ring-blue-500" + /> +
+
+ setPassword(e.target.value)} + className="w-full p-3 border border-gray-300 rounded-md focus:outline-none focus:ring focus:ring-blue-500" + /> +
+ +
+ Don't Have an Account?{" "} + navigate("/signup")} + > + Sign Up + +
+
+
+ ); +} diff --git a/frontend/src/components/Logout.jsx b/frontend/src/components/Logout.jsx index 9cfc4ce..aa80b01 100644 --- a/frontend/src/components/Logout.jsx +++ b/frontend/src/components/Logout.jsx @@ -1,20 +1,25 @@ -import { useContext } from "react"; -import { useNavigate } from "react-router-dom"; -import { TodoContext } from "../context"; - -export function Logout() { - const navigate = useNavigate(); - const { setRender } = useContext(TodoContext); - - const onLogout = () => { - localStorage.removeItem("token"); - setRender((prev) => !prev); - navigate("/"); - }; - - return ( -
- -
- ); -} +import { useContext } from "react"; +import { useNavigate } from "react-router-dom"; +import { TodoContext } from "../context"; + +export function Logout() { + const navigate = useNavigate(); + const { setRender } = useContext(TodoContext); + + const onLogout = () => { + localStorage.removeItem("token"); + setRender((prev) => !prev); + navigate("/"); + }; + + return ( +
+ +
+ ); +} diff --git a/frontend/src/components/NewHome.jsx b/frontend/src/components/NewHome.jsx index a51bffd..f980a08 100644 --- a/frontend/src/components/NewHome.jsx +++ b/frontend/src/components/NewHome.jsx @@ -1,7 +1,7 @@ -export function NewHome() { - return ( -
-

Welcome to my home page

-
- ); -} +export function NewHome() { + return ( +
+

Welcome to TaskNest

+
+ ); +} diff --git a/frontend/src/components/Signup.jsx b/frontend/src/components/Signup.jsx index 3ffef13..8aa62fe 100644 --- a/frontend/src/components/Signup.jsx +++ b/frontend/src/components/Signup.jsx @@ -1,100 +1,100 @@ -import { useState } from "react"; -import axios from "axios"; -import { useNavigate } from "react-router-dom"; - -export function Signup() { - const navigate = useNavigate(); - const [firstName, setFirstName] = useState(""); - const [lastName, setLastName] = useState(""); - const [username, setUserName] = useState(""); - const [password, setPassword] = useState(""); - const [error, setError] = useState(""); // State to handle errors - - const handleSubmit = async (e) => { - e.preventDefault(); // Prevent page reload on form submission - setError(""); // Reset error state - - try { - const result = await axios.post("http://localhost:3000/user/signup", { - firstName, - lastName, - username, - password, - }); - - // console.log(result); - - if (result.data.success) { - localStorage.setItem("token", result.data.token); - navigate("/"); - } else { - setError("Signup failed"); - } - - // Reset input fields - setFirstName(""); - setLastName(""); - setUserName(""); - setPassword(""); - } catch (error) { - window.alert(error.response?.data.message || "Signup failed"); - console.error("Error during signup:", error.response?.data); - setError(error.response?.data.message || "An error occurred"); - } - }; - - return ( -
-
-

Sign Up

- {error &&
{error}
} - {/* Error message display */} -
- setFirstName(e.target.value)} - className="w-full p-3 border border-gray-300 rounded-md focus:outline-none focus:ring focus:ring-blue-500" - /> -
-
- setLastName(e.target.value)} - className="w-full p-3 border border-gray-300 rounded-md focus:outline-none focus:ring focus:ring-blue-500" - /> -
-
- setUserName(e.target.value)} - className="w-full p-3 border border-gray-300 rounded-md focus:outline-none focus:ring focus:ring-blue-500" - /> -
-
- setPassword(e.target.value)} - className="w-full p-3 border border-gray-300 rounded-md focus:outline-none focus:ring focus:ring-blue-500" - /> -
- -
-
- ); -} +import { useState } from "react"; +import axios from "axios"; +import { useNavigate } from "react-router-dom"; + +export function Signup() { + const navigate = useNavigate(); + const [firstName, setFirstName] = useState(""); + const [lastName, setLastName] = useState(""); + const [username, setUserName] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(""); // State to handle errors + + const handleSubmit = async (e) => { + e.preventDefault(); // Prevent page reload on form submission + setError(""); // Reset error state + + try { + const result = await axios.post("http://localhost:3000/user/signup", { + firstName, + lastName, + username, + password, + }); + + // console.log(result); + + if (result.data.success) { + localStorage.setItem("token", result.data.token); + navigate("/"); + } else { + setError("Signup failed"); + } + + // Reset input fields + setFirstName(""); + setLastName(""); + setUserName(""); + setPassword(""); + } catch (error) { + window.alert(error.response?.data.message || "Signup failed"); + console.error("Error during signup:", error.response?.data); + setError(error.response?.data.message || "An error occurred"); + } + }; + + return ( +
+
+

Sign Up

+ {error &&
{error}
} + {/* Error message display */} +
+ setFirstName(e.target.value)} + className="w-full p-3 border border-gray-300 rounded-md focus:outline-none focus:ring focus:ring-blue-500" + /> +
+
+ setLastName(e.target.value)} + className="w-full p-3 border border-gray-300 rounded-md focus:outline-none focus:ring focus:ring-blue-500" + /> +
+
+ setUserName(e.target.value)} + className="w-full p-3 border border-gray-300 rounded-md focus:outline-none focus:ring focus:ring-blue-500" + /> +
+
+ setPassword(e.target.value)} + className="w-full p-3 border border-gray-300 rounded-md focus:outline-none focus:ring focus:ring-blue-500" + /> +
+ +
+
+ ); +} diff --git a/frontend/src/components/Todos.jsx b/frontend/src/components/Todos.jsx index 8bd90bc..57c5364 100644 --- a/frontend/src/components/Todos.jsx +++ b/frontend/src/components/Todos.jsx @@ -1,60 +1,64 @@ -import { useContext, useEffect, useState } from "react"; -import { TodoContext } from "../context"; -import { CompleteButton } from "./CompleteButton"; -import { DeleteButton } from "./DeleteButton"; -import axios from "axios"; -import { AddTodo } from "./AddTodo"; -import { EditTodo } from "./EditTodo"; // Import the new EditTodos component - -export function Todos() { - const { render, setRender } = useContext(TodoContext); - const [todos, setTodos] = useState([]); - - useEffect(() => { - const fetchTodos = async () => { - try { - const response = await axios.get("http://localhost:3000/user/todos", { - headers: { - Authorization: "Bearer " + localStorage.getItem("token"), - }, - }); - setTodos(response.data.todos); - } catch (error) { - console.error("Error fetching todos:", error); - } - }; - fetchTodos(); - }, [render]); - - const handleEditComplete = () => { - setRender((prev) => !prev); // Trigger re-fetching todos on edit completion - }; - - return ( -
-

Your Todos

-
- {todos.map((todo) => ( -
-
- -
- {todo.completed ? "Completed" : "Not Completed"} -
- - -
-
- ))} -
- -
- ); -} +import { useContext, useEffect, useState } from "react"; +import { TodoContext } from "../context"; +import { CompleteButton } from "./CompleteButton"; +import { DeleteButton } from "./DeleteButton"; +import axios from "axios"; +import { AddTodo } from "./AddTodo"; +import { EditTodo } from "./EditTodo"; + +export function Todos() { + const { render, setRender } = useContext(TodoContext); + const [todos, setTodos] = useState([]); + + useEffect(() => { + const fetchTodos = async () => { + try { + const response = await axios.get("http://localhost:3000/user/todos", { + headers: { + Authorization: "Bearer " + localStorage.getItem("token"), + }, + }); + setTodos(response.data.todos); + } catch (error) { + console.error("Error fetching todos:", error); + } + }; + fetchTodos(); + }, [render]); + + const handleEditComplete = () => { + setRender((prev) => !prev); // Trigger re-fetching todos on edit completion + }; + + return ( +
+

+ Your Todos +

+
+ {todos.map((todo) => ( +
+
+ +
+ {todo.completed ? "Completed" : "Not Completed"} +
+
+
+ + +
+
+ ))} +
+ +
+ ); +} diff --git a/frontend/src/context.jsx b/frontend/src/context.jsx index afcadb8..63b4601 100644 --- a/frontend/src/context.jsx +++ b/frontend/src/context.jsx @@ -1,13 +1,13 @@ -import { createContext, useState } from "react"; - -export const TodoContext = createContext(); - -export function TodoRender({ children }) { - const [render, setRender] = useState(false); - - return ( - - {children} - - ); -} +import { createContext, useState } from "react"; + +export const TodoContext = createContext(); + +export function TodoRender({ children }) { + const [render, setRender] = useState(false); + + return ( + + {children} + + ); +} From f436cabd2ba64815f3f17d32add8e9fe548f99e7 Mon Sep 17 00:00:00 2001 From: RashmitTopG Date: Mon, 27 Jan 2025 22:12:55 +0530 Subject: [PATCH 3/3] Modified the workflows --- .github/workflows/build.yml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 74f1470..b338f14 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,8 +16,19 @@ jobs: with: node-version: '20' - - name: Install Dependencies - run: npm install + - name: Install Dependencies in Frontend + run: | + cd frontend + npm install + + - name: Install Dependencies in Backend + run: | + cd backend + npm install - - name: Run Build - run: npm run build + - name: Run Frontend Build + run: | + cd frontend + npm run build + +