From e2b05cf38ff726386a5d558e8005bdca7b1230b3 Mon Sep 17 00:00:00 2001 From: justice chimobi Date: Sat, 16 Nov 2024 17:46:30 +0100 Subject: [PATCH] feat(edit): article/thread replies --- src/api/endpoints/articleEndpoint.ts | 2 + src/api/endpoints/threadEndpoint.ts | 2 + src/hooks/article/useEditArticleComment.ts | 23 ++++++ src/hooks/thread/useEditThreadComment.ts | 23 ++++++ .../Articles/components/commentComponent.tsx | 68 +++++++++++++----- .../Threads/components/commentComponent.tsx | 72 ++++++++++++++----- src/services/articles/index.ts | 5 ++ src/services/threads/index.ts | 5 ++ 8 files changed, 165 insertions(+), 35 deletions(-) create mode 100644 src/hooks/article/useEditArticleComment.ts create mode 100644 src/hooks/thread/useEditThreadComment.ts diff --git a/src/api/endpoints/articleEndpoint.ts b/src/api/endpoints/articleEndpoint.ts index 25d198d..08052b2 100644 --- a/src/api/endpoints/articleEndpoint.ts +++ b/src/api/endpoints/articleEndpoint.ts @@ -18,6 +18,8 @@ export const CREATE_ARTICLE_COMMENT_ENDPOINT = `${API_BASE_URL}/articles`; export const DELETE_ARTICLE_COMMENT_ENDPOINT = `${API_BASE_URL}/articles`; +export const EDIT_ARTICLE_COMMENT_ENDPOINT = `${API_BASE_URL}/articles`; + export const ARTICLE_LIKE_ENDPOINT = `${API_BASE_URL}/articles`; export const ARTICLE_DISLIKE_ENDPOINT = `${API_BASE_URL}/articles`; diff --git a/src/api/endpoints/threadEndpoint.ts b/src/api/endpoints/threadEndpoint.ts index b566315..3ffb0eb 100644 --- a/src/api/endpoints/threadEndpoint.ts +++ b/src/api/endpoints/threadEndpoint.ts @@ -18,6 +18,8 @@ export const CREATE_THREAD_COMMENT_ENDPOINT = `${API_BASE_URL}/threads`; export const DELETE_THREAD_COMMENT_ENDPOINT = `${API_BASE_URL}/threads`; +export const EDIT_THREAD_COMMENT_ENDPOINT = `${API_BASE_URL}/threads`; + export const THREAD_LIKE_ENDPOINT = `${API_BASE_URL}/threads`; export const THREAD_DISLIKE_ENDPOINT = `${API_BASE_URL}/threads`; \ No newline at end of file diff --git a/src/hooks/article/useEditArticleComment.ts b/src/hooks/article/useEditArticleComment.ts new file mode 100644 index 0000000..aaaa45a --- /dev/null +++ b/src/hooks/article/useEditArticleComment.ts @@ -0,0 +1,23 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query' + +import { errorNotification, successNotification } from '@helpers/notification' +import { editArticleComment } from '@services/articles' + +export const useEditArticleComment = () => { + const queryClient = useQueryClient(); + + const editArticlCommentMutation = useMutation({ + mutationFn: editArticleComment, + onSuccess: (data) => { + successNotification(data.message); + + queryClient.invalidateQueries({ queryKey: ['articles']}); + queryClient.invalidateQueries({ queryKey: ['article']}); + }, + onError: (error: any) => { + errorNotification(error?.response?.data?.message) + } + }) + + return { editArticlCommentMutation } +} \ No newline at end of file diff --git a/src/hooks/thread/useEditThreadComment.ts b/src/hooks/thread/useEditThreadComment.ts new file mode 100644 index 0000000..1e64936 --- /dev/null +++ b/src/hooks/thread/useEditThreadComment.ts @@ -0,0 +1,23 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query' + +import { errorNotification, successNotification } from '@helpers/notification' +import { editThreadComment } from '@services/threads' + +export const useEditThreadComment = () => { + const queryClient = useQueryClient(); + + const editThreadCommentMutation = useMutation({ + mutationFn: editThreadComment, + onSuccess: (data) => { + successNotification(data.message); + + queryClient.invalidateQueries({ queryKey: ['threads'] }); + queryClient.invalidateQueries({ queryKey: ['thread'] }) + }, + onError: (error: any) => { + errorNotification(error?.response?.data?.message) + } + }) + + return { editThreadCommentMutation } +} \ No newline at end of file diff --git a/src/pages/Articles/components/commentComponent.tsx b/src/pages/Articles/components/commentComponent.tsx index 0c646b6..e730e4e 100644 --- a/src/pages/Articles/components/commentComponent.tsx +++ b/src/pages/Articles/components/commentComponent.tsx @@ -4,14 +4,16 @@ import { Avatar, Box, Divider, Flex, Text, useDisclosure } from '@chakra-ui/reac import { IArticleComments } from 'src/types' import { useUser } from '@context/userContext' -import { useCreateArticleComment } from '@hooks/article/useCreateArticleComment' import { Button, ContentBlockContent, Editor, ShowLoginModal } from '@components/index' +import { useCreateArticleComment } from '@hooks/article/useCreateArticleComment' import { useDeleteArticleComment } from '@hooks/article/useDeleteArticleComment' +import { useEditArticleComment } from '@hooks/article/useEditArticleComment' const CommentComponent: FunctionComponent<{ comment: IArticleComments, level: number }> = ({ comment, level }) => { const { user } = useUser(); const { createArticlCommentMutation } = useCreateArticleComment(); const { deleteArticleCommentMutation } = useDeleteArticleComment(); + const { editArticlCommentMutation } = useEditArticleComment(); const { isOpen, onOpen, onClose } = useDisclosure(); const { id } = useParams(); @@ -19,31 +21,53 @@ const CommentComponent: FunctionComponent<{ comment: IArticleComments, level: nu const [showCommentInput, setShowCommentInput] = useState(null); const [replyContent, setReplyContent] = useState(''); const [showAllReplies, setShowAllReplies] = useState(false); + const [isEditing, setIsEditing] = useState(false); - const handleShowCommentInput = (articleId: any) => { + const handleShowCommentInput = (articleId: string, isEditingMode: boolean = false, commentText: string) => { if (showCommentInput === articleId) { setShowCommentInput(null); + setReplyContent(""); + setIsEditing(false); } else { setShowCommentInput(articleId); + setIsEditing(isEditingMode); + setReplyContent(isEditingMode ? commentText : ""); } } - const handleCommentSubmit = (event: FormEvent) => { + const handleSubmit = (event: FormEvent) => { event.preventDefault(); - const payload = { - comment: replyContent, - parent_id: comment.id - } - if (replyContent.trim()) { - createArticlCommentMutation.mutate({ data: payload, id: id! }) - setReplyContent(""); + if (!replyContent.trim()) return; + + if (isEditing) { + editArticlCommentMutation.mutate( + { data: { comment: replyContent }, id: comment.id }, + { + onSuccess: () => { + setIsEditing(false) + setShowCommentInput(null); + } + } + ); + } else { + createArticlCommentMutation.mutate( + { data: { comment: replyContent, parent_id: comment.id }, id: id! }, + { + onSuccess: () => { + setReplyContent(""); + setShowCommentInput(null); + } + } + ) } } - const handleToggleReplies = () => { - setShowAllReplies(prev => !prev); - } + const handleToggleReplies = () => setShowAllReplies(prev => !prev); + + const handleIsEditing = (threadId: string, commentText: string) => { + handleShowCommentInput(threadId, true, commentText) + }; return ( @@ -99,7 +123,11 @@ const CommentComponent: FunctionComponent<{ comment: IArticleComments, level: nu )} - handleShowCommentInput(comment.id)} as={"span"}> + { + handleShowCommentInput(comment.id, false, "") + } + } + as={"span"}> Reply @@ -110,6 +138,14 @@ const CommentComponent: FunctionComponent<{ comment: IArticleComments, level: nu Delete )} + + {comment.isOwner && ( + { + handleIsEditing(comment.id, comment.comment); + }}> + Edit + + )} )} @@ -135,7 +171,7 @@ const CommentComponent: FunctionComponent<{ comment: IArticleComments, level: nu )} {showCommentInput === comment.id && ( -
+ Submit diff --git a/src/pages/Threads/components/commentComponent.tsx b/src/pages/Threads/components/commentComponent.tsx index 31918a7..4e89374 100644 --- a/src/pages/Threads/components/commentComponent.tsx +++ b/src/pages/Threads/components/commentComponent.tsx @@ -6,43 +6,67 @@ import { IThreadComments } from 'src/types' import { useUser } from '@context/userContext'; import { Button, ContentBlockContent, Editor, ShowLoginModal } from '@components/index' import { useCreateThreadComment } from '@hooks/thread/useCreateThreadComment' -import { useDeleteThreadComment } from '@hooks/thread/useDeleteThreadComment'; +import { useDeleteThreadComment } from '@hooks/thread/useDeleteThreadComment' +import { useEditThreadComment } from '@hooks/thread/useEditThreadComment' const CommentComponent: FunctionComponent<{ comment: IThreadComments, level: number }> = ({ comment, level }) => { const { user } = useUser(); const { createThreadCommentMutation } = useCreateThreadComment(); const { deleteThreadCommentMutation } = useDeleteThreadComment(); + const { editThreadCommentMutation } = useEditThreadComment(); const { id } = useParams(); const { isOpen, onOpen, onClose } = useDisclosure(); const [showCommentInput, setShowCommentInput] = useState(null); const [replyContent, setReplyContent] = useState(''); const [showAllReplies, setShowAllReplies] = useState(false); + const [isEditing, setIsEditing] = useState(false); - const handleShowCommentInput = (threadId: any) => { + const handleShowCommentInput = (threadId: string, isEditingMode: boolean = false, commentText: string) => { if (showCommentInput === threadId) { setShowCommentInput(null); + setReplyContent(""); + setIsEditing(false); } else { setShowCommentInput(threadId); + setIsEditing(isEditingMode); + setReplyContent(isEditingMode ? commentText : ""); } } - const handleCommentSubmit = (event: FormEvent) => { + const handleSubmit = (event: FormEvent) => { event.preventDefault(); - const payload = { - comment: replyContent, - parent_id: comment.id - }; - if (replyContent.trim()) { - createThreadCommentMutation.mutate({ data: payload, id: id! }); - setReplyContent(""); + if (!replyContent.trim()) return; + + if (isEditing) { + editThreadCommentMutation.mutate( + { data: { comment: replyContent }, id: comment.id }, + { + onSuccess: () => { + setIsEditing(false) + setShowCommentInput(null); + } + } + ); + } else { + createThreadCommentMutation.mutate( + { data: { comment: replyContent, parent_id: comment.id }, id: id! }, + { + onSuccess: () => { + setReplyContent(""); + setShowCommentInput(null); + } + } + ) } } - const handleToggleReplies = () => { - setShowAllReplies(prev => !prev); - } + const handleToggleReplies = () => setShowAllReplies(prev => !prev); + + const handleIsEditing = (threadId: string, commentText: string) => { + handleShowCommentInput(threadId, true, commentText) + }; return ( @@ -76,7 +100,11 @@ const CommentComponent: FunctionComponent<{ comment: IThreadComments, level: num )} - handleShowCommentInput(comment.id)} as={"span"}> + { + handleShowCommentInput(comment.id, false, "") + } + } + as={"span"}> Reply @@ -88,6 +116,14 @@ const CommentComponent: FunctionComponent<{ comment: IThreadComments, level: num Delete )} + + {comment.isOwner && ( + { + handleIsEditing(comment.id, comment.comment); + }}> + Edit + + )} )} @@ -112,12 +148,12 @@ const CommentComponent: FunctionComponent<{ comment: IThreadComments, level: num )} {showCommentInput === comment.id && ( - + @@ -130,7 +166,7 @@ const CommentComponent: FunctionComponent<{ comment: IThreadComments, level: num fontWeight={"semibold"} rounded="sm" isDisabled={!replyContent} - isloading={createThreadCommentMutation.isPending} + isloading={isEditing ? editThreadCommentMutation.isPending : createThreadCommentMutation.isPending} > Submit @@ -157,6 +193,4 @@ const CommentComponent: FunctionComponent<{ comment: IThreadComments, level: num ); }; - - export default CommentComponent; \ No newline at end of file diff --git a/src/services/articles/index.ts b/src/services/articles/index.ts index c82b04f..4feb5d6 100644 --- a/src/services/articles/index.ts +++ b/src/services/articles/index.ts @@ -8,6 +8,7 @@ import { DELETE_ARTICLE_COMMENT_ENDPOINT, DELETE_ARTICLE_ENDPOINT, DELETE_SAVE_ARTICLE_ENDPOINT, + EDIT_ARTICLE_COMMENT_ENDPOINT, EDIT_ARTICLE_ENDPOINT, GET_ALL_ARTICLES_ENDPOINT, GET_PAGINATED_ARTICLES_ENDPOINT, @@ -36,6 +37,10 @@ export const createArticleComment = async ({ data, id }: { data: any, id: string return await handleResponse(axiosInstance.post(`${CREATE_ARTICLE_COMMENT_ENDPOINT}/${id}/comments`, data)); } +export const editArticleComment = async ({ data, id }: { data: any, id: string }) => { + return await handleResponse(axiosInstance.patch(`${EDIT_ARTICLE_COMMENT_ENDPOINT}/${id}/comments`, data)); +} + export const deleteArticleComment = async (id: string) => { return await handleResponse(axiosInstance.delete(`${DELETE_ARTICLE_COMMENT_ENDPOINT}/comments/${id}`)); } diff --git a/src/services/threads/index.ts b/src/services/threads/index.ts index 1b9d6ef..702c855 100644 --- a/src/services/threads/index.ts +++ b/src/services/threads/index.ts @@ -10,6 +10,7 @@ import { THREAD_LIKE_ENDPOINT, GET_PAGINATED_THREADS_ENDPOINT, DELETE_THREAD_COMMENT_ENDPOINT, + EDIT_THREAD_COMMENT_ENDPOINT, } from '@api/index' import { handleResponse, getWithPagination } from '@utils/apiHelpers' import { IThreadRequest, IThreadResponse, MessageResponse, Thread } from 'src/types' @@ -30,6 +31,10 @@ export const createThreadComment = async ({ data, id }: { data: any, id: string return await handleResponse(axiosInstance.post(`${CREATE_THREAD_COMMENT_ENDPOINT}/${id}/comments`, data)); } +export const editThreadComment = async ({ data, id }: { data: any, id: string }) => { + return await handleResponse(axiosInstance.patch(`${EDIT_THREAD_COMMENT_ENDPOINT}/${id}/comments`, data)); +} + export const deleteThreadComment = async (id: string) => { return await handleResponse(axiosInstance.delete(`${DELETE_THREAD_COMMENT_ENDPOINT}/comments/${id}`)); }