From aa4e27df7f8165815cf70228453c464f8b658d3f Mon Sep 17 00:00:00 2001 From: justice chimobi Date: Wed, 6 Nov 2024 23:38:07 +0100 Subject: [PATCH] feat: deleting replies owned --- src/api/axiosInstance.ts | 1 + src/api/endpoints/articleEndpoint.ts | 2 + src/api/endpoints/threadEndpoint.ts | 2 + src/components/FollowSection/index.tsx | 42 ++++++++--------- src/context/userContext.tsx | 31 ++++++++++--- src/hooks/article/useDeleteArticleComment.ts | 23 ++++++++++ src/hooks/auth/useSignOut.ts | 1 + src/hooks/auth/useSignin.ts | 1 + src/hooks/thread/useDeleteThreadComment.ts | 23 ++++++++++ .../Articles/components/commentComponent.tsx | 37 +++++++++++++-- .../Threads/components/commentComponent.tsx | 45 ++++++++++++++----- src/pages/Threads/show/index.tsx | 35 +++++++++++++-- src/services/articles/index.ts | 5 +++ src/services/threads/index.ts | 5 +++ src/types/article/index.ts | 1 + src/types/thread/index.ts | 1 + 16 files changed, 212 insertions(+), 43 deletions(-) create mode 100644 src/hooks/article/useDeleteArticleComment.ts create mode 100644 src/hooks/thread/useDeleteThreadComment.ts diff --git a/src/api/axiosInstance.ts b/src/api/axiosInstance.ts index eb7b25d..45d03c3 100644 --- a/src/api/axiosInstance.ts +++ b/src/api/axiosInstance.ts @@ -27,6 +27,7 @@ const handleErrorResponse = (error: any) => { // Handle 401 (unauthorized) by removing token from localStorage if (error.response?.status === 401 && localStorage.getItem('ucType_')) { localStorage.removeItem('ucType_'); + localStorage.removeItem('clu'); window.location.href = '/auth/login' } return Promise.reject(error); diff --git a/src/api/endpoints/articleEndpoint.ts b/src/api/endpoints/articleEndpoint.ts index b8a1460..25d198d 100644 --- a/src/api/endpoints/articleEndpoint.ts +++ b/src/api/endpoints/articleEndpoint.ts @@ -16,6 +16,8 @@ export const GET_ARTHORED_ARTICLES_ENDPOINT = `${API_BASE_URL}/articles/authored export const CREATE_ARTICLE_COMMENT_ENDPOINT = `${API_BASE_URL}/articles`; +export const DELETE_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 088df23..b566315 100644 --- a/src/api/endpoints/threadEndpoint.ts +++ b/src/api/endpoints/threadEndpoint.ts @@ -16,6 +16,8 @@ export const GET_ARTHORED_THREADS_ENDPOINT = `${API_BASE_URL}/threads/authored`; export const CREATE_THREAD_COMMENT_ENDPOINT = `${API_BASE_URL}/threads`; +export const DELETE_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/components/FollowSection/index.tsx b/src/components/FollowSection/index.tsx index ab191db..ee08cff 100644 --- a/src/components/FollowSection/index.tsx +++ b/src/components/FollowSection/index.tsx @@ -1,8 +1,8 @@ import { FunctionComponent, Fragment } from 'react' import { Link } from 'react-router-dom' -import { Avatar, Box, Flex, Heading, Spacer, Text } from '@chakra-ui/react' +import { Avatar, Box, Flex, Heading, Spacer, Text, useDisclosure } from '@chakra-ui/react' -import { Button } from '@components/index' +import { Button, ShowLoginModal } from '@components/index' import truncate from '@helpers/truncate' import { useCreateFollowUser } from '@hooks/user/useCreateFollowUser' import { useCreateOnFollowUser } from '@hooks/user/useCreateUnFollowUser' @@ -16,20 +16,21 @@ interface IProps { isFetching: boolean; } -const FollowSection: FunctionComponent = ({ - people, - hasMore, - fetchNext, - isFetching +const FollowSection: FunctionComponent = ({ + people, + hasMore, + fetchNext, + isFetching }) => { const { user } = useUser(); const { createFollowUserMutation } = useCreateFollowUser() const { createOnFollowUserMutation } = useCreateOnFollowUser(); + const { isOpen, onOpen, onClose } = useDisclosure(); const handleFollowUnfollow = (userId: string, following: boolean) => { - following - ? createOnFollowUserMutation.mutate(userId) // Unfollow the user - : createFollowUserMutation.mutate(userId); // Follow the user + following + ? createOnFollowUserMutation.mutate(userId) // Unfollow the user + : createFollowUserMutation.mutate(userId); // Follow the user } return ( @@ -64,16 +65,15 @@ const FollowSection: FunctionComponent = ({ )} {!user && ( - - - + )} @@ -104,6 +104,8 @@ const FollowSection: FunctionComponent = ({ )} + + ) } diff --git a/src/context/userContext.tsx b/src/context/userContext.tsx index 82cd112..a638236 100644 --- a/src/context/userContext.tsx +++ b/src/context/userContext.tsx @@ -1,8 +1,10 @@ -import { - createContext, - ReactElement, - ReactNode, - useContext +import { + createContext, + ReactElement, + ReactNode, + useContext, + useEffect, + useState } from 'react' import { useGetUser } from '@hooks/user/useGetUser' @@ -22,7 +24,24 @@ const defaultContextValue: UserContextType = { const UserContext = createContext(defaultContextValue); const UserContextProvider = ({ children }: { children: ReactNode }): ReactElement => { - const { data: user, isLoading, isSuccess } = useGetUser(); + const [user, setUser] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [isSuccess, setIsSuccess] = useState(false); + + const clu = JSON.parse(localStorage.getItem('clu') || "false"); + + const { data, isLoading: loading, isSuccess: success } = useGetUser(); + + useEffect(() => { + if (clu && success && data) { + setUser(data); + setIsLoading(loading); + setIsSuccess(success); + } else { + setIsLoading(false); + setIsSuccess(false); + } + }, [clu, data, loading, success]); return ( diff --git a/src/hooks/article/useDeleteArticleComment.ts b/src/hooks/article/useDeleteArticleComment.ts new file mode 100644 index 0000000..357db33 --- /dev/null +++ b/src/hooks/article/useDeleteArticleComment.ts @@ -0,0 +1,23 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query' + +import { errorNotification, successNotification } from '@helpers/notification' +import { deleteArticleComment } from '@services/articles'; + +export const useDeleteArticleComment = () => { + const queryClient = useQueryClient(); + + const deleteArticleCommentMutation = useMutation({ + mutationFn: deleteArticleComment, + onSuccess: (data) => { + successNotification(data.message); + + queryClient.invalidateQueries({ queryKey: ['articles'] }) + queryClient.invalidateQueries({ queryKey: ['article'] }) + }, + onError: (error: any) => { + errorNotification(error?.response?.data?.message); + } + }) + + return { deleteArticleCommentMutation } +} \ No newline at end of file diff --git a/src/hooks/auth/useSignOut.ts b/src/hooks/auth/useSignOut.ts index 771bd03..002b543 100644 --- a/src/hooks/auth/useSignOut.ts +++ b/src/hooks/auth/useSignOut.ts @@ -13,6 +13,7 @@ export const useSignOut = () => { queryClient.invalidateQueries({ queryKey: ['user'] }) localStorage.removeItem('ucType_'); + localStorage.removeItem('clu'); window.location.href = "/?auth&session=signout"; }, diff --git a/src/hooks/auth/useSignin.ts b/src/hooks/auth/useSignin.ts index 994f610..2ce6187 100644 --- a/src/hooks/auth/useSignin.ts +++ b/src/hooks/auth/useSignin.ts @@ -16,6 +16,7 @@ export const useSignin = () => { queryClient.invalidateQueries({ queryKey: ['user'] }) localStorage.setItem('ucType_', data?.access_token); + localStorage.setItem('clu', JSON.stringify(true)); if (location.pathname === '/auth/login') { navigate('/'); diff --git a/src/hooks/thread/useDeleteThreadComment.ts b/src/hooks/thread/useDeleteThreadComment.ts new file mode 100644 index 0000000..39cec46 --- /dev/null +++ b/src/hooks/thread/useDeleteThreadComment.ts @@ -0,0 +1,23 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query' + +import { errorNotification, successNotification } from '@helpers/notification' +import { deleteThreadComment } from '@services/threads' + +export const useDeleteThreadComment = () => { + const queryClient = useQueryClient(); + + const deleteThreadCommentMutation = useMutation({ + mutationFn: deleteThreadComment, + onSuccess: (data) => { + successNotification(data.message); + + queryClient.invalidateQueries({ queryKey: ['threads'] }) + queryClient.invalidateQueries({ queryKey: ['thread'] }) + }, + onError: (error: any) => { + errorNotification(error?.response?.data?.message); + } + }) + + return { deleteThreadCommentMutation } +} \ No newline at end of file diff --git a/src/pages/Articles/components/commentComponent.tsx b/src/pages/Articles/components/commentComponent.tsx index 8aaeb5e..0c646b6 100644 --- a/src/pages/Articles/components/commentComponent.tsx +++ b/src/pages/Articles/components/commentComponent.tsx @@ -1,15 +1,18 @@ import { FormEvent, Fragment, FunctionComponent, useState } from 'react' import { Link, useParams } from 'react-router-dom' -import { Avatar, Box, Divider, Flex, Text } from '@chakra-ui/react' +import { Avatar, Box, Divider, Flex, Text, useDisclosure } from '@chakra-ui/react' import { IArticleComments } from 'src/types' import { useUser } from '@context/userContext' import { useCreateArticleComment } from '@hooks/article/useCreateArticleComment' -import { Button, ContentBlockContent, Editor } from '@components/index' +import { Button, ContentBlockContent, Editor, ShowLoginModal } from '@components/index' +import { useDeleteArticleComment } from '@hooks/article/useDeleteArticleComment' const CommentComponent: FunctionComponent<{ comment: IArticleComments, level: number }> = ({ comment, level }) => { const { user } = useUser(); const { createArticlCommentMutation } = useCreateArticleComment(); + const { deleteArticleCommentMutation } = useDeleteArticleComment(); + const { isOpen, onOpen, onClose } = useDisclosure(); const { id } = useParams(); @@ -99,10 +102,35 @@ const CommentComponent: FunctionComponent<{ comment: IArticleComments, level: nu handleShowCommentInput(comment.id)} as={"span"}> Reply + + {comment.isOwner && ( + { + deleteArticleCommentMutation.mutate(comment.id) + }}> + Delete + + )} + + )} + {!user && ( + + + + {comment.replies_count && comment.replies_count > 1 ? ( + + replies {comment.replies_count} • + + ) : ( + <> + )} + - {/* Delete */} + + Reply + + )} @@ -149,6 +177,9 @@ const CommentComponent: FunctionComponent<{ comment: IArticleComments, level: nu ))} )} + + + ); }; diff --git a/src/pages/Threads/components/commentComponent.tsx b/src/pages/Threads/components/commentComponent.tsx index cf75f8d..31918a7 100644 --- a/src/pages/Threads/components/commentComponent.tsx +++ b/src/pages/Threads/components/commentComponent.tsx @@ -1,16 +1,19 @@ import { FormEvent, Fragment, FunctionComponent, useState } from 'react' import { Link, useParams } from "react-router-dom"; -import { Avatar, Box, Flex, Spacer, Text } from '@chakra-ui/react' +import { Avatar, Box, Flex, Text, useDisclosure } from '@chakra-ui/react' import { IThreadComments } from 'src/types' import { useUser } from '@context/userContext'; -import { Button, ContentBlockContent, Editor } from '@components/index'; -import { useCreateThreadComment } from '@hooks/thread/useCreateThreadComment'; +import { Button, ContentBlockContent, Editor, ShowLoginModal } from '@components/index' +import { useCreateThreadComment } from '@hooks/thread/useCreateThreadComment' +import { useDeleteThreadComment } from '@hooks/thread/useDeleteThreadComment'; const CommentComponent: FunctionComponent<{ comment: IThreadComments, level: number }> = ({ comment, level }) => { const { user } = useUser(); const { createThreadCommentMutation } = useCreateThreadComment(); + const { deleteThreadCommentMutation } = useDeleteThreadComment(); const { id } = useParams(); + const { isOpen, onOpen, onClose } = useDisclosure(); const [showCommentInput, setShowCommentInput] = useState(null); const [replyContent, setReplyContent] = useState(''); @@ -68,22 +71,42 @@ const CommentComponent: FunctionComponent<{ comment: IThreadComments, level: num replies {comment.replies_count} • - ): ( + ) : ( <> )} handleShowCommentInput(comment.id)} as={"span"}> - Reply + Reply - - - - {/* Delete */} + {comment.isOwner && ( + { + deleteThreadCommentMutation.mutate(comment.id) + }}> + Delete + + )} + + )} + + {!user && ( + + + + {comment.replies_count && comment.replies_count > 1 ? ( + + replies {comment.replies_count} • + + ) : ( + <> + )} + - + + Reply + )} @@ -128,6 +151,8 @@ const CommentComponent: FunctionComponent<{ comment: IThreadComments, level: num )} + + ); }; diff --git a/src/pages/Threads/show/index.tsx b/src/pages/Threads/show/index.tsx index d7f2e5e..91972eb 100644 --- a/src/pages/Threads/show/index.tsx +++ b/src/pages/Threads/show/index.tsx @@ -7,24 +7,26 @@ import { CardHeader, Heading, Text, - useBreakpointValue + useBreakpointValue, + useDisclosure } from '@chakra-ui/react' import { Helmet } from 'react-helmet-async' import { colors } from '../../../colors' -import { FollowCard, NotFound, Skeleton } from '@components/index' +import { FollowCard, NotFound, ShowLoginModal, Skeleton } from '@components/index' // import DiscussionCard from '@pages/Threads/components/discussionCard' import RepliesCard from '@pages/Threads/components/repliesCard' import { useGetSingleThread } from '@hooks/thread/useGetSingleThread' import ThreadCard from '@components/ThreadCard' import truncate from '@helpers/truncate' +import { useUser } from '@context/userContext' const ShowThread: FunctionComponent = () => { const { id } = useParams(); + const { user } = useUser(); const { data, isLoading, isSuccess, error } = useGetSingleThread(id!); - - console.log(data) + const { onClose, onOpen, isOpen } = useDisclosure(); const truncateLenght = useBreakpointValue({ base: 45, md: 30, lg: 70 }); @@ -81,6 +83,29 @@ const ShowThread: FunctionComponent = () => { ) } + + + {!user && ( + + + Sign in to participate! + + Sign in here + + + + )} + { )} + + ) } diff --git a/src/services/articles/index.ts b/src/services/articles/index.ts index cb10738..c82b04f 100644 --- a/src/services/articles/index.ts +++ b/src/services/articles/index.ts @@ -5,6 +5,7 @@ import { CREATE_ARTICLE_COMMENT_ENDPOINT, CREATE_ARTICLE_ENDPOINT, CREATE_SAVE_ARTICLE_ENDPOINT, + DELETE_ARTICLE_COMMENT_ENDPOINT, DELETE_ARTICLE_ENDPOINT, DELETE_SAVE_ARTICLE_ENDPOINT, EDIT_ARTICLE_ENDPOINT, @@ -35,6 +36,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 deleteArticleComment = async (id: string) => { + return await handleResponse(axiosInstance.delete(`${DELETE_ARTICLE_COMMENT_ENDPOINT}/comments/${id}`)); +} + export const createArticleLike = async ({ id }: { id: string }) => { return await handleResponse(axiosInstance.post(`${ARTICLE_LIKE_ENDPOINT}/${id}/likes`)); } diff --git a/src/services/threads/index.ts b/src/services/threads/index.ts index bc61355..1b9d6ef 100644 --- a/src/services/threads/index.ts +++ b/src/services/threads/index.ts @@ -9,6 +9,7 @@ import { THREAD_DISLIKE_ENDPOINT, THREAD_LIKE_ENDPOINT, GET_PAGINATED_THREADS_ENDPOINT, + DELETE_THREAD_COMMENT_ENDPOINT, } from '@api/index' import { handleResponse, getWithPagination } from '@utils/apiHelpers' import { IThreadRequest, IThreadResponse, MessageResponse, Thread } from 'src/types' @@ -29,6 +30,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 deleteThreadComment = async (id: string) => { + return await handleResponse(axiosInstance.delete(`${DELETE_THREAD_COMMENT_ENDPOINT}/comments/${id}`)); +} + export const createThreadLike = async ({ id }: { id: string }) => { return await handleResponse(axiosInstance.post(`${THREAD_LIKE_ENDPOINT}/${id}/likes`)); } diff --git a/src/types/article/index.ts b/src/types/article/index.ts index 2d823f2..a32b7f1 100644 --- a/src/types/article/index.ts +++ b/src/types/article/index.ts @@ -111,5 +111,6 @@ export interface IArticleComments { comment: string; replies_count?: number; created_at?: Pick + isOwner?: boolean; replies?: IArticleReplies[]; } \ No newline at end of file diff --git a/src/types/thread/index.ts b/src/types/thread/index.ts index cac084d..9853d69 100644 --- a/src/types/thread/index.ts +++ b/src/types/thread/index.ts @@ -85,6 +85,7 @@ export interface IThreadComments { user: IThreadUsers; comment: string; created_at?: CreatedAt + isOwner?: boolean; replies_count?: number; replies?: IThreadReplies[]; }