I am Unable to toggle my completed todo on the backend as well as on the frontend

28 views Asked by At

I am unable to toggle my completed todo on the backend as well as on the frontend, If once completed set to true is not returning back to false

import { useContext, useEffect, useState } from "react";
import AuthContext from "../context/AuthContext";

const TodoApp = () => {
  // const [clicked , setClicked] = useState(false)
  const {
    authTokens,
    fetchTasks,
    tasks,
    user,
    createTask,
    updateTask,
    deleteTask,
    setButtonClicked,
    setTasks,
    buttonClicked,
    updateComplete,
  } = useContext(AuthContext);

  // console.log('authTokens in TodoApp:', authTokens);
  useEffect(() => {
    console.log("buttonClicked:", buttonClicked);
    if (user) {
      fetchTasks();
    }
    // console.log("rendered");
    // console.log(fetchTasks());
  }, [user]);

  const handleButtonClick = async (id) => {
    await updateComplete(id);
  };

  return (
    <>
      <form
        onSubmit={(e) => {
          e.preventDefault();
          createTask(e.target.taskTitle.value);
          e.target.taskTitle.value = "";
        }}
        className="bg-white md:w-[60vw] w-[90vw] md:h-24 h-16 md:p-4 p-2 flex gap-2 shadow-lg shadow-indigo-500/50 "
      >
        <input
          type="text"
          name="taskTitle"
          placeholder="Username"
          className="input input-bordered w-[72%] md:h-16 h-11 text-black flex-[3_1_0] "
        />
        <button
          className=" btn btn-success md:h-16 h-6 w-[25%] text-3xl"
          type="submit"
        >
          +
        </button>
      </form>
      <br />
      <div className="h-[calc(100vh-200px)] w-[100vw] mt-2 overflow-y-auto overflow-x-hidden">
        {user && user ? (
          tasks.map((ele) => (
            <div key={ele.id} className=" h-24 w-[100vw] p-1">
              <form
                onSubmit={(e) => e.preventDefault()}
                className=" m-auto border h-16 w-[90vw] md:w-[70vw] md:text-2xl rounded p-3 font-serif relative overflow-hidden"
              >
                {ele.completed ? (
                  <strike className="w-[50vw] text-black" key={ele.id}>
                    {ele.title}
                  </strike>
                ) : (
                  <div className="w-[50vw] text-black" key={ele.id}>
                    {ele.title}
                  </div>
                )}
                <div className="text-xl w-22 absolute right-2 top-2">
                  <button
                    className={`fa-regular fa-circle-check border rounded-s-2xl p-2 md:text-2xl ${
                      ele.completed ? "bg-green-500" : ""
                    }`}
                    type="button" // Use type="button" instead of "submit"
                    onClick={() => handleButtonClick(ele.id)}
                  ></button>
                  <button
                    className="fa-solid fa-pen-to-square border p-2 md:text-2xl"
                    type="submit"
                    onClick={() => {
                      // Prompt the user to enter the new title
                      const newTitle = prompt(
                        "Enter the updated title:",
                        ele.title
                      );
                      if (newTitle) {
                        // Only update if the user provides a new title
                        updateTask(ele.id, { title: newTitle });
                      }
                    }}
                  ></button>
                  <button
                    className="fa-solid fa-trash border rounded-e-2xl p-2 md:text-2xl"
                    type="submit"
                    onClick={() => deleteTask(ele.id)}
                  ></button>
                </div>
              </form>
            </div>
          ))
        ) : (
          <div>Add Todo</div>
        )}
      </div>
    </>
  );
};

export default TodoApp;

My AuthContext.jsx

import {createContext , useState , useEffect} from 'react';
import jwt_decode from 'jwt-decode';
import { useNavigate } from 'react-router-dom'
import jwtDecode from 'jwt-decode';
import  Axios  from 'axios';

const AuthContext = createContext();

export default AuthContext;

// eslint-disable-next-line react/prop-types
export const AuthProvider =({children}) => {

    const [tasks, setTasks] = useState([]);
    const [errors, setErrors] = useState();
    const [buttonClicked, setButtonClicked] = useState(false);
    const [authTokens , setAuthTokens] = useState(() => localStorage.getItem('authTokens') ? JSON.parse(localStorage.getItem('authTokens')) : {})
    const [user , setUser] = useState(() => localStorage.getItem('authTokens') ? jwt_decode(localStorage.getItem('authTokens')) : null)
    const [loading , setLoading] = useState(false);

    let navigate = useNavigate();

    let loginUser = async (e) => {
        e.preventDefault()
        let response = await fetch('http://127.0.0.1:8000/api/token/', {
            method: 'POST',
            headers:{
                'Content-Type' : 'application/json'
            },      
            body:JSON.stringify({'username': e.target.username.value, 'password' : e.target.password.value })
        })
        let data = await response.json()
        
        if (response.status === 200) {
            setAuthTokens(data)
            setUser(jwt_decode(data.access))
            localStorage.setItem('authTokens', JSON.stringify(data))
            navigate('/')
        }else{
            alert('something went wrong!')
        }

    }

    let logoutUser = () => {
        setAuthTokens(null)
        setUser(null)
        localStorage.removeItem('authTokens')
        navigate('/login')
    }

    let updateToken = async () =>{
        console.log("Update Token updated");
        let response = await fetch('http://127.0.0.1:8000/api/token/refresh/', {
            method: 'POST',
            headers:{
                'Content-Type' : 'application/json'
            },      
            body:JSON.stringify({'refresh': authTokens.refresh})
        })
        let data = await response.json()
        
        if (response.status === 200){
            setAuthTokens(data)
            setUser(jwt_decode(data.access))
            localStorage.setItem('authTokens', JSON.stringify(data))
        }else{
            logoutUser();
        }
    }

    const registerUser = async (formData) => {
      try {
        console.log('Registration request data:', formData);
        const response = await fetch('http://127.0.0.1:8000/api/register/', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify(formData),
        });
    
        const data = await response.json();
    
        if (data && response.ok) {
          // Registration successful
          // Perform any necessary actions, e.g., redirect the user to a success page or show a success message
          console.log('Registration successful!');
          navigate('/'); // You can redirect to a success page
        } else {
          // Registration failed due to client-side validation error or server error
          // You can handle the error, e.g., show an error message
          console.log('Registration failed due to error');
          console.log('Error response data:', data.error); // Log the full error response
          setErrors(data.error)    
          // Check if the response contains a detailed error message
        if (data && data.error) {
          console.log(data);
          setErrors(data)
        } else {
          console.log(data);
          setErrors(data)
          // alert('Registration failed. Something went wrong!');
        }
        }
      } catch (error) {
        console.error('Error occurred during registration:', error);
        setErrors('Something went wrong!');
      }
    };

    // State management for TODO App
    const fetchTasks = async () => {
      // console.log(`Bearer ${authTokens.access}`);
        try {
          const response = await Axios.get('http://127.0.0.1:8000/todo/task-list/' , {
            headers: {
              Authorization: `Bearer ${authTokens.access}`,
                // Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjkwNTU2Mjg3LCJpYXQiOjE2OTA1NTU5ODcsImp0aSI6ImQ0NzFiMzRmNzIwZjRjYzViOTIzNGE3YzVhZWQ3ZTQyIiwidXNlcl9pZCI6MSwidXNlcm5hbWUiOiJ6dWxxYXIifQ.pUPPTNxNanTB0qw0ZDOROQ2CrgDRkh5nT6-5oeWaCc8`,
            }
          });
          setTasks(response.data);
          // return response.data
        } catch (error) {
          console.error('Error fetching tasks:', error);
        }
      };

      const createTask = async (title) => {
        try {
          const response = await Axios.post('http://127.0.0.1:8000/todo/task-create/', {
            title: title,
            completed: false,
            buttonClicked: false,
          },
          {
            headers: {
              Authorization: `Bearer ${authTokens.access}`,
            }
          }
          )
          setTasks([...tasks, response.data]);
        } catch (error) {
          console.error('Error creating task:', error);
        }
      };

      const updateTask = async (id, updatedTask) => {
        try {
          await Axios.post(`http://127.0.0.1:8000/todo/task-update/${id}/`, 
          updatedTask,
          {
            headers: {
              Authorization: `Bearer ${authTokens.access}`,
            }
          });
          const updatedTasks = tasks.map((task) =>
            task.id === id ? { ...task, ...updatedTask } : task
          );
          setTasks(updatedTasks);
        } catch (error) {
          console.error('Error updating task:', error);
        }
      };

      const updateComplete = async (id) => {
  try {
    const taskToUpdate = tasks.find((task) => task.id === id);
    const updatedTask = {
      ...taskToUpdate,
      completed: !taskToUpdate.buttonClicked, // Toggle the completed property based on the buttonClicked state
    };
    await Axios.post(
      `http://127.0.0.1:8000/todo/task-update/${id}/`,
      updatedTask,
      {
        headers: {
          Authorization: `Bearer ${authTokens.access}`,
        },
      }
    );

    // Update the local state of tasks with the updated buttonClicked value
    const updatedTasks = tasks.map((task) => (task.id === id ? updatedTask : task));
    setTasks(updatedTasks);
  } catch (error) {
    console.error('Error updating task:', error);
    // Handle the error, e.g., show an error message to the user.
  }
};
          // Since the task is updated on the backend, you don't need to do anything here.

      const deleteTask = async (id) => {
        try {
          await Axios.delete(`http://127.0.0.1:8000/todo/task-delete/${id}/`);
          const updatedTasks = tasks.filter((task) => task.id !== id);
          setTasks(updatedTasks);
        } catch (error) {
          console.error('Error deleting task:', error);
        }
      };
    
    let contextData = {
        user : user,
        loginUser : loginUser,
        logoutUser : logoutUser,
        updateToken : updateToken,
        registerUser : registerUser,
        tasks : tasks,
        setTasks : setTasks,
        fetchTasks : fetchTasks,
        createTask : createTask,
        updateTask : updateTask,
        updateComplete : updateComplete,
        deleteTask : deleteTask,
        errors : errors,
        setButtonClicked : setButtonClicked,
        buttonClicked : buttonClicked,
        authTokens :authTokens,
    }

    useEffect(() => {
      // console.log('authTokens:', authTokens);
        if (authTokens) {
            setUser(jwtDecode(authTokens.access))
        }
        setLoading(false);

    }, [authTokens , loading])

    return(
        <AuthContext.Provider value={contextData}>
            {loading ?<div>Loading...</div> : children}
        </AuthContext.Provider>
    )
}

My Views.py

from rest_framework.response import Response
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from .serializers import TaskSerializer
from .models import Task


# Create your views here.

@api_view(['GET'])
def ApiOverView(request):
    api_urls = {
        'List' : '/task-list/',
        'DetailView' : '/detail-view/',
        'Create' : '/task-create/',
        'Update' : '/task-update/',
        'Delete' : '/task-delete/'
    }

    return Response(api_urls)

@api_view(['GET'])
def TaskList(request):
    user = request.user
    print('user get: ' + str(request.user))
    tasks = Task.objects.filter(user = user)
    serializer = TaskSerializer(tasks, many = True)
    return Response(serializer.data)

@api_view(['GET'])
def DetailView(request , pk):
    tasks = Task.objects.get(id = pk)
    serializer = TaskSerializer(tasks, many = False)
    return Response(serializer.data)

@api_view(['POST'])
def TaskCreate(request):
    data = request.data   
    data['user'] = request.user.id
    serializer = TaskSerializer(data = data)

    try:
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            # print('Post Created ' + serializer.data)
    except Exception as e: 
        print(e)

    return Response(serializer.data , status=201)

@api_view(['POST'])
def TaskUpdate(request , pk):
    data = request.data   
    data['user'] = request.user.id
    task = Task.objects.get(id = pk)

    task.completed = data['completed']
    task.save()

    serializer = TaskSerializer(instance=task ,data=request.data)

    try:
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            print('Post updated ' + serializer.data)
    except Exception as e: 
        print(e)
    # return Response({'message': 'Post updated', 'data': data}, status=202)
    return Response(serializer.data , status=202)

@api_view(['DELETE'])
def TaskDelete(request , pk):
    print("id " , pk)
    data = request.data   
    data['user'] = request.user.id
    task = Task.objects.get(id = pk)
    task.delete()


    return Response('Item deleted')

Please give the solution

I am expecting that once the user click on the handleButtonClick the completed field will toggle both in the Backend and the Frontend

0

There are 0 answers