Backend
npm i bcryptjs cors dotenv express joi jsonwebtoken mongoose nodemon
folder structure
controllers
- controllers/authController.js
const User = require("../models/User");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const { registerSchema, loginSchema } = require("../utils/authValidation");
const generateToken = (userId) => {
return jwt.sign({ userId }, process.env.JWT_SECRET, { expiresIn: "7d" });
};
const registerUser = async (req, res) => {
const { error } = registerSchema.validate(req.body);
if (error) return res.status(400).json({ msg: error.details[0].message });
try {
let user = await User.findOne({ email: req.body.email });
if (user) return res.status(400).json({ msg: "User already exists" });
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(req.body.password, salt);
user = new User({ ...req.body, password: hashedPassword });
await user.save();
res.status(201).json({
msg: "User registered successfully",
token: generateToken(user.id),
user: { id: user.id, name: user.name, email: user.email },
});
} catch (error) {
res.status(500).json({ msg: "Server error" });
}
};
const loginUser = async (req, res) => {
const { error } = loginSchema.validate(req.body);
if (error) return res.status(400).json({ msg: error.details[0].message });
try {
const user = await User.findOne({ email: req.body.email });
if (!user) return res.status(400).json({ msg: "Invalid credentials" });
const isMatch = await bcrypt.compare(req.body.password, user.password);
if (!isMatch) return res.status(400).json({ msg: "Invalid credentials" });
res.json({
msg: "User login successfully",
token: generateToken(user.id),
user: { id: user.id, name: user.name, email: user.email },
});
} catch (error) {
res.status(500).json({ msg: "Server error" });
}
};
module.exports = { registerUser, loginUser };
controllers/todoController.js
const Todo = require("../models/Todo");
const { todoSchema } = require("../utils/todoValidation");
const createTodo = async (req, res) => {
const { error } = todoSchema.validate(req.body);
if (error) return res.status(400).json({ msg: error.details[0].message });
try {
const todo = new Todo({ ...req.body, user: req.user });
await todo.save();
res.status(201).json(todo);
} catch (error) {
res.status(500).json({ msg: "Server error" });
}
};
const getTodos = async (req, res) => {
try {
const todos = await Todo.find({ user: req.user }).sort({ createdAt: -1 });
res.json(todos);
} catch (error) {
res.status(500).json({ msg: "Server error" });
}
};
const updateTodo = async (req, res) => {
const { error } = todoSchema.validate(req.body);
if (error) return res.status(400).json({ msg: error.details[0].message });
try {
const todo = await Todo.findOneAndUpdate(
{ _id: req.params.id, user: req.user },
req.body,
{ new: true }
);
if (!todo) return res.status(404).json({ msg: "Todo not found" });
res.json(todo);
} catch (error) {
res.status(500).json({ msg: "Server error" });
}
};
const deleteTodo = async (req, res) => {
try {
const todo = await Todo.findOneAndDelete({
_id: req.params.id,
user: req.user,
});
if (!todo) return res.status(404).json({ msg: "Todo not found" });
res.json({ msg: "Todo deleted successfully" });
} catch (error) {
res.status(500).json({ msg: "Server error" });
}
};
module.exports = { createTodo, getTodos, updateTodo, deleteTodo };
middleware:-
middleware/authMiddleware.js
const jwt = require("jsonwebtoken");
const protect = (req, res, next) => {
const authHeader = req.header("Authorization");
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return res.status(401).json({ msg: "No token, authorization denied" });
}
const token = authHeader.split(" ")[1]; // Extract token after "Bearer "
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded.userId;
next();
} catch (error) {
res.status(401).json({ msg: "Invalid token" });
}
};
module.exports = protect;
**models:-**
models/Todo.js
const mongoose = require("mongoose");
const todoSchema = new mongoose.Schema(
{
user: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true },
title: { type: String, required: true },
description: { type: String },
priority: {
type: String,
enum: ["low", "medium", "high"],
default: "medium",
},
status: {
type: String,
enum: ["pending", "completed"],
default: "pending",
},
dueDate: { type: Date, required: true },
},
{ timestamps: true }
);
module.exports = mongoose.model("Todo", todoSchema);
models/User.js
const mongoose = require("mongoose");
const userSchema = new mongoose.Schema(
{
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
},
{ timestamps: true }
);
module.exports = mongoose.model("User", userSchema);
routes:-
routes/authRoutes.js
const express = require("express");
const { registerUser, loginUser } = require("../controllers/authController");
const router = express.Router();
router.post("/register", registerUser);
router.post("/login", loginUser);
module.exports = router;
routes/todoRoutes.js
const express = require("express");
const {
createTodo,
getTodos,
updateTodo,
deleteTodo,
} = require("../controllers/todoController");
const protect = require("../middleware/authMiddleware");
const router = express.Router();
router.post("/", protect, createTodo);
router.get("/", protect, getTodos);
router.put("/:id", protect, updateTodo);
router.delete("/:id", protect, deleteTodo);
module.exports = router;
utils:-
utils/authValidation.js
const Joi = require("joi");
const registerSchema = Joi.object({
name: Joi.string().min(3).max(30).required(),
email: Joi.string().email().required(),
password: Joi.string().min(6).required(),
});
const loginSchema = Joi.object({
email: Joi.string().email().required(),
password: Joi.string().min(6).required(),
});
module.exports = { registerSchema, loginSchema };
utils/jwt.js
const generateToken = (userId) => {
return jwt.sign({ id: userId }, process.env.JWT_SECRET, {
expiresIn: "1d",
});
};
const verifyToken = (token) => {
return jwt.verify(token, process.env.JWT_SECRET);
};
utils/todoValidation.js
const Joi = require("joi");
const todoSchema = Joi.object({
title: Joi.string().min(3).max(100).required(),
description: Joi.string().max(500).allow(""),
priority: Joi.string().valid("low", "medium", "high").default("medium"),
status: Joi.string().valid("pending", "completed").default("pending"),
dueDate: Joi.date().greater("now").required(),
});
module.exports = { todoSchema };
.env
PORT=5000
MONGO_URI=
JWT_SECRET=i-am-utsav-ioopen-source
config.js
const mongoose = require("mongoose");
require("dotenv").config();
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log("MongoDB Connected...");
} catch (error) {
console.error("MongoDB Connection Failed:", error);
process.exit(1);
}
};
module.exports = connectDB;
server.js
const express = require("express");
const dotenv = require("dotenv");
const cors = require("cors");
const connectDB = require("./config");
const authRoutes = require("./routes/authRoutes");
const todoRoutes = require("./routes/todoRoutes");
dotenv.config();
connectDB();
const app = express();
app.use(express.json());
app.use(cors());
app.use("/api/auth", authRoutes);
app.use("/api/todos", todoRoutes);
app.get("/", (req, res) => {
res.send("API is running...");
});
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
package.json
{
"name": "backend",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js",
"dev": "nodemon server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"bcryptjs": "^3.0.2",
"cors": "^2.8.5",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"express-validator": "^7.2.1",
"joi": "^17.13.3",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.10.1"
},
"devDependencies": {
"nodemon": "^3.1.9"
}
}
Frontend
main.jsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App.jsx";
// import AppRouter from "./AppRouter.jsx";
import { Toaster } from "react-hot-toast";
createRoot(document.getElementById("root")).render(
{/* */}
);
app.jsx
// import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
// import Login from "./pages/Login";
// import Register from "./pages/Register";
// import Dashboard from "./pages/Dashboard";
// import { ProtectedRoute } from "./authMiddleware";
import AuthProvider from "./provider/authProvider";
import Routes from "./routes";
function App() {
return (
//
//
// } />
// } />
// {/* } /> */}
//
//
//
// }
// />
//
//
);
}
export default App;
AppRouter.jsx
import {
BrowserRouter as Router,
Routes,
Route,
Navigate,
} from "react-router-dom";
import { useState, useEffect } from "react";
import Login from "./pages/Login";
import Register from "./pages/Register";
import Dashboard from "./pages/Dashboard";
// import NotFound from "./pages/NotFound";
const AppRouter = () => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
useEffect(() => {
const token = localStorage.getItem("token");
setIsAuthenticated(!!token);
}, []);
return (
: }
/>
} />
} />
{/* } /> */}
);
};
export default AppRouter;
authMiddleware.js
import { useNavigate } from "react-router-dom";
export const ProtectedRoute = ({ children }) => {
const navigate = useNavigate();
const token = localStorage.getItem("token");
return token ? children : navigate("/");
};
index.css
@tailwind utilities;
html.dark {
background-color: #121212;
color: white;
}
.dark .bg-white {
background-color: #1e1e1e;
}
.dark .border {
border-color: #333;
}
.dark .shadow {
box-shadow: 0px 4px 6px rgba(255, 255, 255, 0.1);
}
App.css
@import "tailwindcss";
utils/axios.js
import axios from "axios";
const API = axios.create({
baseURL: "http://localhost:5000/api",
});
API.interceptors.request.use(
(config) => {
const token = localStorage.getItem("token");
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
},
(error) => Promise.reject(error)
);
export default API;
routes
routes/ProtectedRoute.jsx
import { Navigate, Outlet } from "react-router-dom";
import { useAuth } from "../provider/authProvider";
export const ProtectedRoute = () => {
const { token } = useAuth();
// Check if the user is authenticated
if (!token) {
// If not authenticated, redirect to the login page
return ;
}
// If authenticated, render the child routes
return ;
};
routes/index.jsx
import { RouterProvider, createBrowserRouter } from "react-router-dom";
import { useAuth } from "../provider/authProvider";
import { ProtectedRoute } from "./ProtectedRoute";
import Login from "../pages/Login";
// import Logout from "../pages/Logout";
import Dashboard from "../pages/Dashboard";
import TodoList from "../components/second/TodoList";
const Routes = () => {
const { token } = useAuth();
// Define public routes accessible to all users
const routesForPublic = [
{
path: "/service",
element: Service Page,
},
{
path: "/about-us",
element: About Us,
},
];
// Define routes accessible only to authenticated users
const routesForAuthenticatedOnly = [
{
path: "/",
element: , // Wrap the component in ProtectedRoute
children: [
{
path: "",
element: ,
// element: ,
},
{
path: "/profile",
element: User Profile,
},
// {
// path: "/logout",
// element: ,
// },
],
},
];
// Define routes accessible only to non-authenticated users
const routesForNotAuthenticatedOnly = [
// {
// path: "/",
// element: Home Page,
// },
{
path: "/login",
element: ,
},
];
// Combine and conditionally include routes based on authentication status
const router = createBrowserRouter([
...routesForPublic,
...(!token ? routesForNotAuthenticatedOnly : []),
...routesForAuthenticatedOnly,
]);
// Provide the router configuration using RouterProvider
return ;
};
export default Routes;
provider
provider/authProvider.jsx
import axios from "axios";
import { createContext, useContext, useEffect, useMemo, useState } from "react";
const AuthContext = createContext();
const AuthProvider = ({ children }) => {
// State to hold the authentication token
const [token, setToken_] = useState(localStorage.getItem("token"));
// Function to set the authentication token
const setToken = (newToken) => {
setToken_(newToken);
};
useEffect(() => {
if (token) {
axios.defaults.headers.common["Authorization"] = "Bearer " + token;
localStorage.setItem("token", token);
} else {
delete axios.defaults.headers.common["Authorization"];
localStorage.removeItem("token");
}
}, [token]);
// Memoized value of the authentication context
const contextValue = useMemo(
() => ({
token,
setToken,
}),
[token]
);
// Provide the authentication context to the children components
return (
{children}
);
};
export const useAuth = () => {
return useContext(AuthContext);
};
export default AuthProvider;
pages
pages/Dashboard.jsx
import { useState, useEffect } from "react";
import API from "../utils/axios";
import TodoModal from "../components/TodoModal";
import toast from "react-hot-toast";
import DarkModeToggle from "../components/DarkModeToggle";
import { useAuth } from "../provider/authProvider";
import { useNavigate } from "react-router-dom";
const Dashboard = () => {
const { setToken } = useAuth();
const navigate = useNavigate();
const [todos, setTodos] = useState([]);
const [modalOpen, setModalOpen] = useState(false);
const [editTodo, setEditTodo] = useState(null);
const [statusFilter, setStatusFilter] = useState("all");
const [sortOrder, setSortOrder] = useState("newest");
const [filteredTodos, setFilteredTodos] = useState([]);
const [loading, setLoading] = useState(false); // Add loading state
const fetchTodos = async () => {
setLoading(true); // Start loading
try {
const res = await API.get("/todos");
setTodos(res.data);
} catch (err) {
console.error(err);
} finally {
setLoading(false); // End loading
}
};
useEffect(() => {
fetchTodos();
}, []);
useEffect(() => {
let updatedTodos = [...todos];
if (statusFilter !== "all") {
updatedTodos = updatedTodos.filter((todo) =>
statusFilter === "completed"
? todo.status === "completed"
: todo.status === "pending"
);
}
if (sortOrder === "newest") {
updatedTodos.sort((a, b) => new Date(b.dueDate) - new Date(a.dueDate));
} else {
updatedTodos.sort((a, b) => new Date(a.dueDate) - new Date(b.dueDate));
}
setFilteredTodos(updatedTodos);
}, [statusFilter, sortOrder, todos]);
const deleteTodo = async (id) => {
try {
await API.delete(`/todos/${id}`);
setTodos((prev) => prev.filter((todo) => todo._id !== id));
toast.success("To-Do deleted successfully!");
} catch (err) {
console.log(err);
toast.error("Failed to delete To-Do!");
}
};
// const logOut = () => {
// localStorage.removeItem("token");
// window.location.href = "/";
// };
const handleLogout = () => {
setToken();
navigate("/", { replace: true });
};
return (
Logout
{" "}
To-Do List
setModalOpen(true)}
aria-label="Add a new to-do"
>
Add To-Do
setStatusFilter(e.target.value)}
value={statusFilter}
>
All
Completed
Pending
setSortOrder(e.target.value)}
value={sortOrder}
>
Newest First
Oldest First
setModalOpen(true)}
>
Add To-Do
{
loading ? (
Loading todos...
) : (
Title
Description
Priority
Due Date
Status
Actions
{/* {todos.map((todo) => (
{todo.title}
{todo.description}
{todo.priority}
{new Date(todo.dueDate).toLocaleDateString()}
{todo.status === "completed" ? (
Completed
) : (
Pending
)}
{
setEditTodo(todo);
setModalOpen(true);
}}
>
Edit
deleteTodo(todo._id)}
>
Delete
))} */}
{filteredTodos.map((todo) => (
{todo.title}
{todo.description}
{todo.priority}
{new Date(todo.dueDate).toLocaleDateString()}
{todo.status === "completed" ? (
Completed
) : (
Pending
)}
{
setEditTodo(todo);
setModalOpen(true);
}}
>
Edit
deleteTodo(todo._id)}
>
Delete
))}
)}
{
setModalOpen(false);
setEditTodo(null);
}}
refreshTodos={fetchTodos}
editTodo={editTodo}
/>
);
};
export default Dashboard;
pages/Login.jsx
import { useNavigate } from "react-router-dom";
import toast from "react-hot-toast";
import { Formik } from "formik";
import * as Yup from "yup";
import API from "../utils/axios";
import { useAuth } from "../provider/authProvider";
const LoginInitialValues = {
email: "",
password: "",
};
const LogInSchema = Yup.object().shape({
email: Yup.string()
.email("Invalid email address")
.required("Email is required"),
password: Yup.string()
.required("Password is required")
.matches(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#\$%\^&\*])(?=.{8,})/,
"Password must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, one number, and one special character"
),
});
const Login = () => {
const navigate = useNavigate();
const { setToken } = useAuth();
// const handleLogin = async (e) => {
// e.preventDefault();
// try {
// const { data } = await axios.post(
// "http://localhost:5000/api/auth/login",
// { email, password }
// );
// localStorage.setItem("token", data.token);
// toast.success("Login successful!");
// navigate("/dashboard");
// } catch (error) {
// toast.error(error.response?.data?.msg || "Login failed");
// }
// };
const handleSubmit = async (values, { setSubmitting }) => {
try {
const response = await API.post("/auth/login", {
email: values.email,
password: values.password,
});
console.log("LLLL", response);
// localStorage.setItem("token", response.data.token);
toast.success(response?.data?.data?.msg || "Login successfully");
// navigate("/dashboard");
setToken(response?.data?.token);
navigate("/", { replace: true });
} catch (error) {
// console.log("error.....", error);
toast.error(error?.response?.data?.msg || "Login failed");
}
setSubmitting(false);
};
return (
{/* */}
handleSubmit(values, setSubmitting)
}
>
{({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
isSubmitting,
isValid,
}) => (
Login
{errors.email && touched.email && errors.email}
{errors.password && touched.password && errors.password}
Login
navigate("/register")}
>
Register
{/* */}
)}
);
};
export default Login;
pages/Register.jsx
import { useNavigate } from "react-router-dom";
import toast from "react-hot-toast";
import { Formik } from "formik";
import * as Yup from "yup";
import API from "../utils/axios";
const LoginInitialValues = {
name: "",
email: "",
password: "",
confirmPassword: "",
};
const LogInSchema = Yup.object().shape({
name: Yup.string().required("Username is required"),
email: Yup.string()
.email("Invalid email address")
.required("Email is required"),
password: Yup.string()
.required("Password is required")
.matches(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#\$%\^&\*])(?=.{8,})/,
"Password must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, one number, and one special character"
),
confirmPassword: Yup.string()
.oneOf([Yup.ref("password")], "Passwords must match")
.min(8, "Confirm password must be at least 8 characters")
.required("Confirm password is required"),
});
const Register = () => {
const navigator = useNavigate();
const handleSubmit = async (values, { setSubmitting }) => {
try {
const response = await API.post("/auth/register", {
name: values.name,
email: values.email,
password: values.password,
});
toast.success(response?.data?.data?.msg || "Registration successfully");
navigator("/");
} catch (error) {
console.log("error.....", error);
toast.error(error?.response?.data?.msg || "Registration failed");
}
setSubmitting(false);
};
return (
{/* */}
handleSubmit(values, setSubmitting)
}
>
{({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
isSubmitting,
isValid,
}) => (
Register
{errors.name && touched.name && errors.name}
{errors.email && touched.email && errors.email}
{errors.password && touched.password && errors.password}
{errors.confirmPassword &&
touched.confirmPassword &&
errors.confirmPassword}
Register
navigator("/")}
>
Login
)}
);
};
export default Register;
components
components/DarkModeToggle.jsx
import { useState, useEffect } from "react";
const DarkModeToggle = () => {
const [darkMode, setDarkMode] = useState(
localStorage.getItem("theme") === "dark"
);
useEffect(() => {
if (darkMode) {
document.documentElement.classList.add("dark");
localStorage.setItem("theme", "dark");
} else {
document.documentElement.classList.remove("dark");
localStorage.setItem("theme", "light");
}
}, [darkMode]);
return (
setDarkMode(!darkMode)}
className="p-2 bg-gray-500 text-white rounded"
>
{darkMode ? "Light Mode" : "Dark Mode"}
);
};
export default DarkModeToggle;
components/TodoModal.jsx
import { useEffect } from "react";
import { useFormik } from "formik";
import * as Yup from "yup";
import API from "../utils/axios";
import toast from "react-hot-toast";
const TodoModal = ({ isOpen, onClose, refreshTodos, editTodo }) => {
const formik = useFormik({
initialValues: {
title: editTodo ? editTodo.title : "",
description: editTodo ? editTodo.description : "",
priority: editTodo ? editTodo.priority : "low",
dueDate: editTodo ? editTodo.dueDate.split("T")[0] : "",
status: editTodo ? editTodo.status : "pending",
},
validationSchema: Yup.object({
title: Yup.string().required("Title is required"),
priority: Yup.string().oneOf(["low", "medium", "high"]).required(),
dueDate: Yup.date().required("Due date is required"),
status: Yup.string().oneOf(["pending", "completed"]).required(),
}),
onSubmit: async (values) => {
try {
if (editTodo) {
await API.put(`/todos/${editTodo._id}`, values);
toast.success("To-Do updated successfully!");
} else {
await API.post("/todos", values);
toast.success("To-Do added successfully!");
}
refreshTodos();
onClose();
} catch (err) {
toast.error("Something went wrong!");
}
},
});
useEffect(() => {
if (editTodo) {
formik.setValues({
title: editTodo.title,
priority: editTodo.priority,
dueDate: editTodo.dueDate.split("T")[0],
status: editTodo.status,
description: editTodo.description,
});
}
}, [editTodo]);
return (
isOpen && (
{editTodo ? "Edit" : "Add"} To-Do
{formik.touched.title && formik.errors.title && (
{formik.errors.title}
)}
{formik.touched.description && formik.errors.description && (
{formik.errors.description}
)}
Low
Medium
High
Pending
Completed
{formik.touched.dueDate && formik.errors.dueDate && (
{formik.errors.dueDate}
)}
{editTodo ? "Update" : "Add"} To-Do
)
);
};
export default TodoModal;
components/second/TodoForm.jsx
// TodoForm.jsx
import React from 'react';
import { Formik, Form, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup';
import API from '../../utils/axios';
const TodoForm = ({ fetchTodos, todo = {}, isUpdate = false, closeModal }) => {
const validationSchema = Yup.object({
title: Yup.string()
.min(2, 'Title must be at least 2 characters')
.max(50, 'Title must be less than 50 characters')
.required('Title is required'),
description: Yup.string()
.max(200, 'Description must be less than 200 characters')
.required('Description is required'),
dueDate: Yup.date()
.required('Due date is required')
.min(new Date(), 'Due date cannot be in the past'),
priority: Yup.string()
.oneOf(['low', 'medium', 'high'], 'Invalid priority')
.required('Priority is required'),
status: Yup.string().oneOf(["pending", "completed"])
.required('Status is required'),
});
const initialValues = {
title: todo.title || '',
description: todo.description || '',
dueDate: todo.dueDate ? new Date(todo.dueDate).toISOString().split('T')[0] : '',
priority: todo.priority || 'low',
status: todo.status || 'pending',
};
const handleSubmit = async (values, { resetForm }) => {
try {
if (isUpdate) {
await API.put(`/todos/${todo._id}`, values);
closeModal();
} else {
await API.post('/todos', values);
resetForm();
}
fetchTodos();
} catch (error) {
console.error('Error submitting todo:', error);
}
};
return (
{({ isSubmitting }) => (
Title
Description
Due Date
Priority
Low
Medium
High
Status
Pending
Completed
{isUpdate ? 'Update' : 'Create'} Todo
)}
);
};
export default TodoForm;
components/second/TodoList.jsx
// TodoList.jsx
import React, { useState, useEffect } from 'react';
import API from '../../utils/axios';
import TodoForm from './TodoForm';
import { useAuth } from '../../provider/authProvider';
import { useNavigate } from 'react-router-dom';
const TodoList = () => {
const { setToken } = useAuth();
const navigate = useNavigate();
const [todos, setTodos] = useState([]);
const [showUpdateModal, setShowUpdateModal] = useState(false);
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [selectedTodo, setSelectedTodo] = useState(null);
useEffect(() => {
fetchTodos();
}, []);
const fetchTodos = async () => {
try {
const response = await API.get('/todos');
setTodos(response.data);
} catch (error) {
console.error('Error fetching todos:', error);
}
};
const handleDeleteClick = (todo) => {
setSelectedTodo(todo);
setShowDeleteModal(true);
};
const handleUpdateClick = (todo) => {
setSelectedTodo(todo);
setShowUpdateModal(true);
};
const handleDelete = async () => {
try {
await API.delete(`/todos/${selectedTodo._id}`);
setTodos(todos.filter(todo => todo._id !== selectedTodo._id));
setShowDeleteModal(false);
} catch (error) {
console.error('Error deleting todo:', error);
}
};
const handleLogout = () => {
setToken();
navigate("/", { replace: true });
};
return (
Logout
Todo List
Title
Description
Due Date
Priority
Status
Actions
{todos.map(todo => (
{todo.title}
{todo.description}
{new Date(todo.dueDate).toLocaleDateString()}
{todo.priority}
{todo.status}
handleUpdateClick(todo)}
className="bg-yellow-500 text-white px-3 py-1 rounded mr-2 hover:bg-yellow-600"
>
Edit
handleDeleteClick(todo)}
className="bg-red-500 text-white px-3 py-1 rounded hover:bg-red-600"
>
Delete
))}
{showUpdateModal && (
Update Todo
setShowUpdateModal(false)} className="text-gray-500 hover:text-gray-700">
×
setShowUpdateModal(false)}
/>
)}
{showDeleteModal && (
Confirm Delete
Are you sure you want to delete "{selectedTodo?.title}"?
setShowDeleteModal(false)}
className="bg-gray-300 px-4 py-2 rounded hover:bg-gray-400"
>
Cancel
Delete
)}
);
};
export default TodoList;