Search code examples
reactjsasp.net-mvcredux

GET https://localhost:5001/purchaseOrder/[object%20Object]/comments 404 (Not Found)


i need to create a commentbox in purchears order i cretate the functions for crud comments in purchears order

   public record CommentDto (long Id ,long IdPO,long IdUser,string Text,DateTime? dateCreate);
        public record CommentCreateDto (long Id ,long IdPO,long IdUser,string Text,DateTime? dateCreate);
        public record CommentUpdateDto (long Id ,long IdPO,long IdUser,string Text,DateTime? dateCreate);

        public PurchaseOrderController(IMediator mediator)
        {
            _mediator = mediator;
           
        }

        private readonly IMediator _mediator;
        
     /// <summary>
    /// Get Comments for a Purchase Order
    /// </summary>
    /// <param name="id">Purchase Order ID</param>
    /// <returns></returns>
    [ProducesResponseType(typeof(IEnumerable<CommentDto>), (int)HttpStatusCode.OK)]
    [ProducesResponseType((int)HttpStatusCode.NotFound)]
    [ProducesResponseType((int)HttpStatusCode.InternalServerError)]
    [HttpGet("{id:long}/comments")]
    public async Task<IActionResult> GetCommentsAsync([FromHeader(Name = TenantMiddleware.TENANT_HEADER)] long id)
    {
        var comments = await _mediator.Send(new GetPurchaseOrderComments.Query(id));
        
        if (comments == null)
            return NotFound();

        return Ok(comments.Adapt<IEnumerable<CommentDto>>());
    }

    /// <summary>
    /// Add Comment to Purchase Order
    /// </summary>
    /// <param name="id">Purchase Order ID</param>
    /// <param name="usrID">UsersID</param>
    /// <param name="commentDto">Comment data</param>
    /// <returns></returns>
   // POST: api/PurchaseOrders/1/Comments
    // POST: api/PurchaseOrders/1/Comments
    
    [ProducesResponseType(typeof(int), (int)HttpStatusCode.Created)]
    [ProducesResponseType((int)HttpStatusCode.NotFound)]
    [ProducesResponseType((int)HttpStatusCode.Forbidden)]
    [ProducesResponseType((int)HttpStatusCode.BadRequest)]
    [ProducesResponseType((int)HttpStatusCode.InternalServerError)]
    [HttpPost("{id:long}/Comments")]
  public async Task<IActionResult> AddCommentAsync([FromHeader(Name = TenantMiddleware.TENANT_HEADER)] long id,long usrID, CommentCreateDto commentDto)
{
    // Assuming CommentCreateDto has a Text property
    var command = new AddPurchaseOrderComment.Command(id,usrID, commentDto.Text);

    var result = await _mediator.Send(command);

    return result switch
    {
        long commentId => Created(new Uri($"/po/{id}/comments/{commentId}", UriKind.Relative), result)
    };
}


    /// <summary>
    /// Update Comment for Purchase Order
    /// </summary>
    /// <param name="id">Purchase Order ID</param>
    /// <param name="commentId">Comment ID</param>
    /// <param name="commentDto">Updated Comment data</param>
    /// <returns></returns>
    [HttpPut("{id:long}/comments/{commentId:long}")]
    [ProducesResponseType(typeof(int), (int)HttpStatusCode.NoContent)]
    [ProducesResponseType((int)HttpStatusCode.Forbidden)]
    [ProducesResponseType((int)HttpStatusCode.NotFound)]
    [ProducesResponseType((int)HttpStatusCode.BadRequest)]
    [ProducesResponseType((int)HttpStatusCode.InternalServerError)]
    public async Task<IActionResult> UpdateCommentAsync([FromHeader(Name = TenantMiddleware.TENANT_HEADER)] long id, long commentId, CommentUpdateDto commentDto)
    {
         var command = commentDto.Adapt<UpdatePurchaseOrderComment.Command>();
            var result = await _mediator.Send(command with { Id = id });

            return result switch
            {
            
               _ => Accepted(new Uri($"/po/{id}", UriKind.Relative), result),
            };
           
    }

    /// <summary>
    /// Delete Comment for Purchase Order
    /// </summary>
    /// <param name="id">Purchase Order ID</param>
    /// <returns></returns>
    [HttpDelete("{id:long}/comments/{commentId:long}")]
    [ProducesResponseType(typeof(int), (int)HttpStatusCode.NoContent)]
    [ProducesResponseType((int)HttpStatusCode.Forbidden)]
    [ProducesResponseType((int)HttpStatusCode.NotFound)]
    [ProducesResponseType((int)HttpStatusCode.BadRequest)]
    [ProducesResponseType((int)HttpStatusCode.InternalServerError)]
    public async Task<IActionResult> DeleteCommentAsync([FromHeader(Name = TenantMiddleware.TENANT_HEADER)] long id)
    {
        var result = await _mediator.Send(new DeletePurchaseOrderComment.Command(id));

            return result switch
            {
                DeleteResult.NotFound => NotFound(),
                DeleteResult.Forbidden => Forbid(),
                _ => NoContent(),
            };
    }

and the request redux

import axios from "axios";
import config from "../../_config.json"


const getHeaders = (token) => {
    return {
      'Authorization': 'Bearer ' + token,
      'x-tenant-id': localStorage.getItem('tenantId'),
      'Content-Type': 'application/json'
    }
}


// **Get Comments** (Read)
export const getComments = async (payload) => {

     let token = localStorage.getItem('token');
  
    return await axios.request({
      method: "get",
      headers: getHeaders(token),
      withCredentials: true,
      url: `${config.apiUrl}/purchaseOrder/${payload}/comments`,
    });
  };
  
  // **Add Comment** (Create)
  export const addComment = async (payload) => {
  
    
    let token = localStorage.getItem('token');
  
    // Construct API request with headers, URL, and data (comment content)
    return await axios.request({
      method: "post",
      headers: getHeaders(token),
      withCredentials: true,
      url: `${config.apiUrl}/PurchaseOrder/${payload}/comments`,
      data: payload,
    });
  };
  
  // **Update Comment** (Update)
  export const updateComment = async (payload, commentId, updatedText) => {
   
    let token = localStorage.getItem('token');
  
    // Construct API request with headers, URL, and data (updated text)
    return await axios.request({
      method: "put",
      headers: getHeaders(token),
      withCredentials: true,
      url: `${config.apiUrl}/PurchaseOrders/${payload.id}/comments/${commentId}`,
      data: { text: updatedText },
    });
  };
  
  // **Delete Comment** (Delete)
  export const deleteComment = async (payload, commentId) => {
    // Get token from local storage
    let token = localStorage.getItem('token');
  
    return await axios.request({
      method: "delete",
      headers: getHeaders(token),
      withCredentials: true,
      url: `${config.apiUrl}/PurchaseOrders/${payload.id}/comments/${commentId}`,
    });
  };


export const editPo = async (payload) => {

    let token = localStorage.getItem('token')

    return await axios.request({
        method: "put",
        headers: getHeaders(token),
        withCredentials: true,
        url: `${config.apiUrl}/purchaseOrder/${payload.id}`,
        data: payload.data
    })
};



export const editPoStatus = async (payload) => {

    let token = localStorage.getItem('token')

    return await axios.request({
        method: "put",
        headers: getHeaders(token),
        withCredentials: true,
        url: `${config.apiUrl}/purchaseOrder/${payload.id}/status`,
        data: payload.data
    })
};


export const deletePo = async (payload) => {

    let token = localStorage.getItem('token')

    return await axios.request({
        method: "delete",
        headers: getHeaders(token),
        withCredentials: true,
        url: `${config.apiUrl}/purchaseOrder/${payload}`,
    })
};

and for poSlice

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import {   getComments,addComment,updateComment, deleteComment} from "../request/poRequest";
export const getCommentsAsync = createAsyncThunk( 'comment/getComments', async (payload) => {
      try {
        const response = await getComments(payload); // Adjust payload if needed
        return response.data;
      } catch (err) {
        return thunkAPI.rejectWithValue({ error: err.response.data });
      }
    }
  )

export const addCommentAsync = createAsyncThunk(
    'comment/addComment',
    async (payload, thunkAPI) => {
      try {
        const response = await addComment(payload);
        // Potentially update state here, but likely handled in consuming components
        return response.data;
      } catch (err) {
        return thunkAPI.rejectWithValue({ error: err.response.data });
      }
    }
  )

  export const editPoCommentAsync = createAsyncThunk(
    'comment/editPoComment',
    async (payload, thunkAPI) => {
      try {
        const response = await updateComment(payload);
        // Potentially update state with edited comment data (implementation in editPoComment)
        return response.data;
      } catch (err) {
        return thunkAPI.rejectWithValue({ error: err.response.data });
      }
    }
  )

  export const deletePoCommentAsync = createAsyncThunk(
    'comment/deletePoComment',
    async (payload, thunkAPI) => {
      try {
        const response = await deleteComment(payload);
        // No explicit state update needed, component might reflect deletion in UI
        return response.data;
      } catch (err) {
        return thunkAPI.rejectWithValue({ error: err.response.data });
      }
    }
  )
  
  

const initialState = {
    info: [],
    item: {},
    status: 'idle',
    type: null,
    errors: {},
    comments: [],
}

//Reducers
const poSlice = createSlice({
    name: "po",
    initialState: initialState,
    reducers: {},

    extraReducers(builder) {

   // Add Commen
        builder
             .addCase(addCommentAsync.pending, (state, action) => {
               return { ...state, status: "loading", type: "post", errors: {} };
             })
             .addCase(addCommentAsync.fulfilled, (state, action) => {
               return { ...state, type: "post", status: "succeded" };
             })
             .addCase(addCommentAsync.rejected, (state, action) => {
               return { ...state, errors: action.error.errors, type: "post", status: "failed" };
             });
    // Edit Comment
         builder
        .addCase(editPoCommentAsync.pending, (state, action) => {
            return { ...state, status: "loading", type: "put", errors: {} }
        })
        .addCase(editPoCommentAsync.fulfilled, (state, action) => {
            return { ...state, type: "put", status: "succeded" }
        })
        .addCase(editPoCommentAsync.rejected, (state, action) => {
            return { ...state, errors: payload.error.errors, type: "put", status: "failed" }
        });

    // Delete Comment
          builder
        .addCase(deletePoCommentAsync.pending, (state, action) => {
            return { ...state, status: "loading", type: "delete", errors: {} }
        })
        .addCase(deletePoCommentAsync.fulfilled, (state, action) => {
            return { ...state, type: "delete", status: "succeded" }
        })
        .addCase(deletePoCommentAsync.rejected, (state, action) => {
            return { ...state, errors: payload.error.errors, type: "delete", status: "failed" }
        });
     // get Comment
        builder
        .addCase(getCommentsAsync.pending, (state, action) => {
              return { ...state, status: "loading", type: "get_comments", errors: {} };
        })
        .addCase(getCommentsAsync.fulfilled, (state, action) => {
               return { ...state, comments: action.payload, type: "get_comments", status: "succeded" };
         })
        .addCase(getCommentsAsync.rejected, (state, action) => {
               return { ...state, errors: action.error.errors, type: "get_comments", status: "failed" };
         });

    }
})

export default poSlice.reducer;

and for my compenant Comments

// @ts-nocheck
import { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import CommentForm from "./CommentForm";
import Comment from "./Comment";
import { addCommentAsync, deletePoCommentAsync, editPoCommentAsync, getCommentsAsync } from "../../../redux/ducks/poSlice";

const Comments = ({ commentsUrl, currentUserId }) => {
  const dispatch = useDispatch();
  const comments = useSelector((state) => state.po.comments); // Access comments from state using useSelector

  const [activeComment, setActiveComment] = useState(null);

  useEffect(() => {
    dispatch(getCommentsAsync({ poId: 1 })); // Replace poId with actual value
  }, [dispatch]); // Add dispatch as a dependency to avoid infinite loops

  const addComment = (text, parentId) => {
    dispatch(addCommentAsync({ poId: 1, text, parentId })); // Adjust payload
  };

  const updateComment = (text, commentId) => {
    dispatch(editPoCommentAsync({ poId: 1, commentId, text })); // Adjust payload
  };

  const deleteComment = (commentId) => {
    if (window.confirm("Are you sure you want to remove comment?")) {
      dispatch(deletePoCommentAsync({ poId: 1, commentId })); // Adjust payload
    }
  };

  const [rootComments, setRootComments] = useState([]);
  useEffect(() => {
    // Assuming comments contain a parentId property
    setRootComments(comments.filter((comment) => comment.parentId === null));
  }, [comments]); // Recalculate root comments when comments change

  
  return (
    <div className="comments">
      <h3 className="comments-title">Comments</h3>
      <CommentForm submitLabel="Write" handleSubmit={addComment} />
      <div className="comments-container">
        {rootComments.map((rootComment) => ( // Use rootComments for clarity
          <Comment
            key={rootComment.id}
            comment={rootComment}
            activeComment={activeComment}
            setActiveComment={setActiveComment}
            addComment={addComment}
            deleteComment={deleteComment}
            updateComment={updateComment}
            currentUserId={currentUserId}
          />
        ))}
      </div>
    </div>
  );
};

export default Comments;

i have this erreur in the exucution for my crud :

poRequest.tsx:71

GET https://localhost:5001/purchaseOrder/[object%20Object]/comments 404 (Not Found)

i try to change paylod whith payload.id give me undifnded


Solution

  • Issue

    The code is dispatching a payload value that is an object

    dispatch(getCommentsAsync({ poId: 1 }));
    

    and not accessing the poId property for the API request, so the entire payload object is stringified for the request, e.g. "[object object]", and when URL encoded is "[object%20Object]".

    Solution

    • Either correctly unpack the poId property from the action payload:

      export const getComments = async ({ poId }) => {
        const token = localStorage.getItem('token');
      
        return await axios.request({
          method: "get",
          headers: getHeaders(token),
          withCredentials: true,
          url: `${config.apiUrl}/purchaseOrder/${poId}/comments`,
        });
      };
      

      or

      export const getCommentsAsync = createAsyncThunk(
        'comment/getComments',
        async ({ poId }) => {
          try {
            const { data } = await getComments(poId);
            return data;
          } catch (err) {
            return thunkAPI.rejectWithValue({ error: err.response.data });
          }
        }
      );
      
    • Or just don't object-i-fy the action payload to begin with:

      dispatch(getCommentsAsync(1));