From 82f564f8ce17e419e2b1be7f50bb28f09e852b86 Mon Sep 17 00:00:00 2001 From: Rykkel <220993G@mymail.nyp.edu.sg> Date: Tue, 13 Aug 2024 10:07:24 +0800 Subject: [PATCH] User edit/delete comment function --- client/src/components/CommentInputModule.tsx | 1 - client/src/components/CommentsModule.tsx | 151 ++++++++++++++++++- server/routes/post.js | 28 ++++ 3 files changed, 175 insertions(+), 5 deletions(-) diff --git a/client/src/components/CommentInputModule.tsx b/client/src/components/CommentInputModule.tsx index 14235af..50a5138 100644 --- a/client/src/components/CommentInputModule.tsx +++ b/client/src/components/CommentInputModule.tsx @@ -9,7 +9,6 @@ import { retrieveUserInformation } from "../security/users"; import { Button } from "@nextui-org/react"; import { PaperAirplaneIcon } from "../icons"; - const validationSchema = Yup.object({ content: Yup.string() .trim() diff --git a/client/src/components/CommentsModule.tsx b/client/src/components/CommentsModule.tsx index 97e17eb..2530f90 100644 --- a/client/src/components/CommentsModule.tsx +++ b/client/src/components/CommentsModule.tsx @@ -2,13 +2,16 @@ import instance from "../security/http"; import config from "../config"; import { useParams } from "react-router-dom"; import { useEffect, useState } from "react"; -import { Avatar, Button, Dropdown, DropdownItem, DropdownMenu, DropdownTrigger, User } from "@nextui-org/react"; -import { ChatBubbleOvalLeftEllipsisIcon, EllipsisHorizontalIcon, HandThumbsUpIcon } from "../icons"; +import { Avatar, Button, Dropdown, DropdownItem, DropdownMenu, DropdownTrigger, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader, useDisclosure, User } from "@nextui-org/react"; +import { ChatBubbleOvalLeftEllipsisIcon, EllipsisHorizontalIcon, HandThumbsUpIcon, PaperAirplaneIcon } from "../icons"; +import * as Yup from "yup"; +import NextUIFormikTextarea from "./NextUIFormikTextarea"; +import { Form, Formik, FormikHelpers } from "formik"; interface Comment { id: string; content: string; - user: User; // Make user optional + user: User; } interface User { @@ -17,9 +20,21 @@ interface User { lastName: string; } +const validationSchema = Yup.object({ + content: Yup.string() + .trim() + .min(3, "Content must be at least 3 characters") + .max(500, "Content must be at most 500 characters") + .required("Content is required"), +}); + export default function CommentsModule() { const { id } = useParams(); const [commentList, setCommentList] = useState([]); + // To track/manage comment being edited + const [editingCommentId, setEditingCommentId] = useState(null); + const { isOpen, onOpen, onOpenChange } = useDisclosure(); + const [commentToDelete, setCommentToDelete] = useState(null); let postId = id @@ -39,6 +54,53 @@ export default function CommentsModule() { getComments(); }, [id]); + const handleEditClick = (comment: Comment) => { + setEditingCommentId(comment.id); + }; + + const submitComment = async (values: { content: string }, { resetForm }: FormikHelpers<{ content: string }>) => { + // Find the comment that matches the `editingCommentId` to get userId and postId + const commentBeingEdited = commentList.find(comment => comment.id === editingCommentId); + if (!commentBeingEdited) { + console.error("Comment not found"); + return; + } + const data = { + content: values.content, + userId: commentBeingEdited.user.id, // Include the userId + postId: postId // Include the postId + }; + + const response = await instance.put(`${config.serverAddress}/post/comments/${editingCommentId}`, data); + + if (response.status === 200) { + console.log("Comment updated successfully", response.data); + resetForm(); + setEditingCommentId(null); + getComments(); + } else { + console.error("Failed to update the comment", response.data); + } + }; + + const handleDeleteClick = (commentId: string) => { + setCommentToDelete(commentId); + onOpen(); + }; + const handleDeleteConfirm = async () => { + if (!commentToDelete) return; + + const response = await instance.delete(`${config.serverAddress}/post/comments/${commentToDelete}`); + if (response.status === 200) { + console.log("Comment deleted successfully", response.data); + onOpenChange(); + setCommentToDelete(null); + getComments(); + } else { + console.error("Failed to delete the comment", response.data); + } + }; + return ( <>
@@ -71,7 +133,66 @@ export default function CommentsModule() {
{user.firstName} {user.lastName}
-
{comment.content}
+ {editingCommentId === comment.id ? ( + + {({ isValid, dirty }) => ( +
+
+ + +
+
+ )} +
+ ) : ( +
{comment.content}
+ )} +
+
+
+ +
+ + + +
+ + handleEditClick(comment)} + > + Edit + + handleDeleteClick(comment.id)} + > + Delete + + +
+
@@ -86,6 +207,28 @@ export default function CommentsModule() {
+ + + {(onClose) => ( + <> + + Confirm Delete + + +

Are you sure you want to delete this comment?

+
+ + + + + + )} +
+
); }; \ No newline at end of file diff --git a/server/routes/post.js b/server/routes/post.js index c42c8e9..c1fdbaf 100644 --- a/server/routes/post.js +++ b/server/routes/post.js @@ -301,6 +301,34 @@ router.post("/:id/comments", async (req, res) => { } }); +router.put("/comments/:id", async (req, res) => { + let { id } = req.params; + let data = req.body; + + // Validate request body + let validationSchema = yup.object({ + content: yup.string().trim().min(3).max(500).required(), + userId: yup.string().required(), + postId: yup.string().required(), + }); + try { + console.log("Validating schema..."); + data = await validationSchema.validate(data, { abortEarly: false }); + + // Ensure comment exists + let comment = await Comment.findByPk(id); + if (!comment) { + res.sendStatus(404); // Comment not found + return; + } + + let result = await comment.update(data); + res.json({ message: "Comment updated successfully", comment }); + } catch (err) { + res.status(400).json({ errors: err.errors }); + } +}) + router.get("/:id/getComments", async (req, res) => { let id = req.params.id;