Skip to content

Commit

Permalink
Merge pull request #71 from SWM-METEOR/FIN-393
Browse files Browse the repository at this point in the history
댓글 수정 및 삭제
  • Loading branch information
YuriKwon authored Oct 5, 2023
2 parents 0c8c811 + 8ede626 commit 4fa82e9
Show file tree
Hide file tree
Showing 12 changed files with 505 additions and 74 deletions.
7 changes: 7 additions & 0 deletions app/(auth)/logout/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@

import { getCookie, deleteCookie } from 'cookies-next';
import { useRouter } from 'next/navigation';
import { useQueryClient } from '@tanstack/react-query';

import axiosInstance from '@/utils/axios';

export default function LogoutPage() {
const router = useRouter();
const queryClient = useQueryClient();

const accessToken = getCookie('accessToken');
const refreshToken = getCookie('refreshToken');

Expand All @@ -15,6 +19,9 @@ export default function LogoutPage() {
.then(() => {
deleteCookie('accessToken');
deleteCookie('refreshToken');
queryClient.setQueryData(['nickname'], '');
queryClient.setQueryData(['blogName'], '');

router.push('/');
})
.catch((error) => {
Expand Down
7 changes: 4 additions & 3 deletions app/additional-info/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@ import { useState } from 'react';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
import { useForm, SubmitHandler } from 'react-hook-form';
import { useQueryClient } from '@tanstack/react-query';

import axiosInstance from '@/utils/axios';
import CustomButton from '@/components/common/CustomButton';
import ImageUpload from '@/components/common/ImageUpload';
import InputNickname from '@/components/user/AdditionalInfo/InputNickname';
import InputBlogName from '@/components/user/AdditionalInfo/InputBlogName';
import { userBlogNameStore } from '@/store/user';
import AdditionalInfoType from '@/types/user';

export default function AdditionalInfoPage({ params }: { params: { nickname: string } }) {
const router = useRouter();
const { setBlogName } = userBlogNameStore();
const queryClient = useQueryClient();

const [isValidNickname, setIsValidNickname] = useState(true);
const [isValidBlogName, setIsValidBlogName] = useState(true);

Expand All @@ -39,7 +40,7 @@ export default function AdditionalInfoPage({ params }: { params: { nickname: str
.post('users/additional-info', data)
.then((res) => {
// 유저 정보(블로그 이름 수정)
setBlogName(data.blogName);
queryClient.invalidateQueries(['blogName']);
router.push('/');
})
.catch((err) => {
Expand Down
7 changes: 2 additions & 5 deletions components/comments/CommentEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';

import axiosInstance from '@/utils/axios';
import useToast from '@/hooks/toast';

interface ErrorResponse {
status: number;
}
import { ServerErrorResponse } from '@/types/error';

interface PropsType {
pageParams: { nickname: string; articleTitle: string };
Expand Down Expand Up @@ -59,7 +56,7 @@ export default function CommentEditor({ pageParams, type }: PropsType) {
setCharCount(0);
queryClient.invalidateQueries(['comments', type, pageParams]);
},
onError: (err: AxiosError<ErrorResponse>) => {
onError: (err: AxiosError<ServerErrorResponse>) => {
if (!err.response) {
return;
}
Expand Down
110 changes: 104 additions & 6 deletions components/comments/CommentList/CommentListContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,43 @@
'use client';
import { useQuery } from '@tanstack/react-query';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { AxiosError } from 'axios';
import Swal from 'sweetalert2';
import { getCookie } from 'cookies-next';

import axiosInstance from '@/utils/axios';
import CommentListView from '@/components/comments/CommentList/CommentListView';
import { CommentListType } from '@/types/comment';
import useToast from '@/hooks/toast';
import { ServerErrorResponse } from '@/types/error';

interface PropsType {
pageParams: { nickname: string; articleTitle: string };
type: 'reply' | 'answer';
}

export default function CommentListContainer({ pageParams, type }: PropsType) {
const requestUrl = {
reply: `/articles/replies/${pageParams.nickname}/${pageParams.articleTitle}`,
answer: '/answers',
};
const queryClient = useQueryClient();
const accessToken = getCookie('accessToken');
const [showErrorToast] = useToast();

const nicknameQuery = useQuery(
['nickname'],
async () => {
const res = await axiosInstance.get('/users/nickname');
return res.data.data.nickname;
},
{
staleTime: Infinity,
enabled: !!accessToken,
}
);

const fetchCommentList = async () => {
const requestUrl = {
reply: `/articles/replies/${pageParams.nickname}/${pageParams.articleTitle}`,
answer: '/answers',
};

const res = await axiosInstance.get(requestUrl[type]);
return res.data.data;
};
Expand All @@ -29,6 +50,75 @@ export default function CommentListContainer({ pageParams, type }: PropsType) {
staleTime: 0,
});

const updateComment = async ({
commentId,
updatedContent,
}: {
commentId: number;
updatedContent: string;
}) => {
const requestUrl = {
reply: `/articles/replies/edit/${commentId}`,
answer: '/answers',
};

const res = await axiosInstance.post(requestUrl[type], {
content: updatedContent,
});

return res.data;
};

const deleteComment = async (commentId: number) => {
const requestUrl = {
reply: `/articles/replies/delete/${commentId}`,
answer: '/answers',
};

let responseData = null;

await Swal.fire({
title: '정말로 삭제하시겠습니까?',
text: '삭제한 글은 복구할 수 없습니다.',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: '삭제',
cancelButtonText: '취소',
}).then(async (result) => {
if (result.isConfirmed) {
const res = await axiosInstance.post(requestUrl[type]);
responseData = res.data;
}
});

return responseData;
};

const mutationUpdateComment = useMutation(updateComment, {
onSuccess: () => {
queryClient.invalidateQueries(['comments', type, pageParams]);
console.log('Comment updated successfully!');
},
onError: (error) => {
console.error('Error updating comment:', error);
},
});

const mutationDeleteComment = useMutation(deleteComment, {
onSuccess: () => {
queryClient.invalidateQueries(['comments', type, pageParams]);
},
onError: (error: AxiosError<ServerErrorResponse>) => {
if (error.response?.data.code === '400_REPLY_NOT_WRITER') {
showErrorToast('작성자만 삭제할 수 있습니다.');

return;
}
},
});

if (isLoading) {
return null;
}
Expand All @@ -37,5 +127,13 @@ export default function CommentListContainer({ pageParams, type }: PropsType) {
return null;
}

return <CommentListView commentList={commentList} />;
return (
<CommentListView
type={type}
userNickname={nicknameQuery.data || ''}
commentList={commentList}
updateComment={mutationUpdateComment.mutate}
deleteComment={mutationDeleteComment.mutate}
/>
);
}
59 changes: 55 additions & 4 deletions components/comments/CommentList/CommentListView.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,52 @@
'use client';

import { useState } from 'react';
import Image from 'next/image';
import Link from 'next/link';

import UpdateCommentEditor from '@/components/comments/UpdateCommentEditor';
import { CommentListType } from '@/types/comment';

interface PropsType<T> {
type: 'reply' | 'answer';
userNickname: string;
commentList: T | null;
updateComment: (params: { commentId: number; updatedContent: string }) => void;
deleteComment: (commentId: number) => void;
}

export default function CommentListView({
type,
userNickname,
commentList,
deleteComment,
}: PropsType<CommentListType<'reply' | 'answer'>>) {
const [isUpdateMode, setIsUpdateMode] = useState(false);
const [updatedCommentId, setUpdatedCommentId] = useState<number | null>(null);

if (!commentList) return null;

const commentListItems =
'replyList' in commentList ? commentList.replyList : commentList.answerList;

if (commentListItems.length === 0) return null;

const handleUpdateComment = (commentId: number) => {
if (!isUpdateMode) {
setIsUpdateMode(true);
setUpdatedCommentId(commentId);
return;
}
};

const handleDeleteComment = async (commentId: number) => {
try {
await deleteComment(commentId);
} catch (error) {
console.error('Error deleting the comment:', error);
}
};

return (
<div>
<p className="flex gap-[5px] text-[16px]">
Expand All @@ -27,7 +56,7 @@ export default function CommentListView({
<hr className="w-full text-[#DDDDDD] mt-[20px]" />
{commentListItems.map((comment, index) => (
<>
<div key={index} className="py-[20px]">
<div key={index} className="py-[20px] w-full">
<div className="flex gap-[10px] items-center">
<div className="relative w-[30px] h-[30px] rounded-[8px] overflow-hidden flex-shrink-0">
<Image
Expand All @@ -42,10 +71,32 @@ export default function CommentListView({
{comment.nickname}
</Link>
<span className="text-[13px] text-[#999999]">{comment.createdDate}</span>
{/* <button className="text-[#999999] font-semibold text-[14px] ml-auto">수정</button>
<button className="text-[#999999] font-semibold text-[14px]">삭제</button> */}
{userNickname === comment.nickname && (
<button
className="text-[#999999] font-semibold text-[14px] ml-auto"
onClick={() => handleUpdateComment(comment.id)}
>
수정
</button>
)}
{userNickname === comment.nickname && (
<button
className="text-[#999999] font-semibold text-[14px]"
onClick={() => handleDeleteComment(comment.id)}
>
삭제
</button>
)}
</div>
<p className="pl-[40px] pt-[11px]">{comment.content}</p>
{isUpdateMode && updatedCommentId === comment.id ? (
<UpdateCommentEditor
type={type}
commentId={comment.id}
setIsUpdateMode={setIsUpdateMode}
/>
) : (
<div className="break-words pl-[40px] pt-[11px] w-full">{comment.content}</div>
)}
</div>
<hr className="w-full text-[#DDDDDD]" />
</>
Expand Down
Loading

0 comments on commit 4fa82e9

Please sign in to comment.