Backend

npm i bcryptjs cors dotenv express joi jsonwebtoken mongoose nodemon
folder structure

Image description

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;