diff --git a/src/page/post-detail-page.tsx b/src/page/post-detail-page.tsx index 1b74e09..edc4a56 100644 --- a/src/page/post-detail-page.tsx +++ b/src/page/post-detail-page.tsx @@ -19,6 +19,9 @@ import { import { getMyMemberId } from '@shared/utils/auth'; import { COMMENT_QUERY_OPTIONS } from '@shared/api/domain/comments/query'; import { queryClient } from '@app/providers/query-client'; +import { COMMENT_MUTATION_OPTIONS } from '@shared/api/domain/comments/query'; +import { useState } from 'react'; +import XIcon from '@shared/assets/icon/x.svg?react'; const handleShare = async () => { const url = window.location.href; @@ -40,6 +43,10 @@ const PostDetailPage = () => { const navigate = useNavigate(); const { feedId } = useParams(); const numericFeedId = Number(feedId); + const [comment, setComment] = useState(''); + const [parentId, setParentId] = useState(null); + const [replyTarget, setReplyTarget] = useState(null); + const { mutate: applyParticipation } = useMutation({ ...PARTICIPATION_MUTATION_OPTIONS.APPLY(), onSuccess: () => { @@ -91,6 +98,18 @@ const PostDetailPage = () => { }); }, }); + const { mutate: createComment } = useMutation({ + ...COMMENT_MUTATION_OPTIONS.CREATE(), + onSuccess: () => { + setComment(''); + setParentId(null); + setReplyTarget(null); + + queryClient.invalidateQueries({ + queryKey: ['comments', numericFeedId], + }); + }, + }); const { data, isLoading } = useQuery( FEED_QUERY_OPTIONS.DETAIL(numericFeedId), @@ -142,6 +161,10 @@ const PostDetailPage = () => { comments={commentsData ?? []} participants={participants} isOwner={isOwner} + onReply={(commentId, nickname) => { + setParentId(commentId); + setReplyTarget(nickname ?? null); + }} onChangeApproval={(participationId, status) => { if (status === 'approved') { approveParticipation(participationId); @@ -159,12 +182,46 @@ const PostDetailPage = () => { )} -
- - } - /> +
+ {replyTarget && ( +
+ ↳{replyTarget}님에게 답글 작성중... + + +
+ )} + +
+ setComment(e.target.value)} + /> + + } + onClick={() => { + if (!comment.trim()) return; + + createComment({ + feedId: numericFeedId, + body: { + description: comment, + parentId: parentId ?? undefined, + }, + }); + }} + /> +
); diff --git a/src/shared/api/domain/comments/query.ts b/src/shared/api/domain/comments/query.ts index 5a5f5bc..9816f7b 100644 --- a/src/shared/api/domain/comments/query.ts +++ b/src/shared/api/domain/comments/query.ts @@ -1,12 +1,21 @@ -import { queryOptions } from '@tanstack/react-query'; +import { queryOptions, mutationOptions } from '@tanstack/react-query'; import { api } from '@shared/api/config/instance'; import { END_POINT } from '@shared/api/end-point'; -import type { GetCommentsResponse } from '@shared/types/comments/type'; +import type { + CreateCommentRequest, + GetCommentsResponse, +} from '@shared/types/comments/type'; const getComments = async (feedId: number) => { return api.get(END_POINT.FEED.COMMENTS(feedId)).json(); }; +const createComment = async (feedId: number, body: CreateCommentRequest) => { + return api.post(END_POINT.FEED.COMMENTS(feedId), { + json: body, + }); +}; + export const COMMENT_QUERY_OPTIONS = { LIST: (feedId: number) => queryOptions({ @@ -14,3 +23,16 @@ export const COMMENT_QUERY_OPTIONS = { queryFn: () => getComments(feedId), }), }; + +export const COMMENT_MUTATION_OPTIONS = { + CREATE: () => + mutationOptions({ + mutationFn: ({ + feedId, + body, + }: { + feedId: number; + body: CreateCommentRequest; + }) => createComment(feedId, body), + }), +}; diff --git a/src/shared/api/end-point.ts b/src/shared/api/end-point.ts index 8150945..05dbba9 100644 --- a/src/shared/api/end-point.ts +++ b/src/shared/api/end-point.ts @@ -3,7 +3,8 @@ export const END_POINT = { LIST: 'api/feeds', DETAIL: (feedId: number) => `api/feeds/${feedId}`, PARTICIPATION: (feedId: number) => `api/feeds/${feedId}/participations`, - COMMENTS: (feedId: number) => `api/feeds/${feedId}/comments`, // ✅ 추가 + COMMENTS: (feedId: number) => `api/feeds/${feedId}/comments`, + CREATE: (feedId: number) => `api/feeds/${feedId}/comments`, }, PARTICIPATION: { APPROVE: (id: number) => `api/participations/${id}/approve`, diff --git a/src/shared/types/comments/type.ts b/src/shared/types/comments/type.ts index 07858eb..6bad85c 100644 --- a/src/shared/types/comments/type.ts +++ b/src/shared/types/comments/type.ts @@ -2,3 +2,6 @@ import type { paths } from '@shared/types/schema'; export type GetCommentsResponse = paths['/api/feeds/{feedId}/comments']['get']['responses']['200']['content']['*/*']; + +export type CreateCommentRequest = + paths['/api/feeds/{feedId}/comments']['post']['requestBody']['content']['application/json']; diff --git a/src/widgets/postDetail/comment/comment-item.tsx b/src/widgets/postDetail/comment/comment-item.tsx index e59a74d..43b0fa1 100644 --- a/src/widgets/postDetail/comment/comment-item.tsx +++ b/src/widgets/postDetail/comment/comment-item.tsx @@ -11,6 +11,7 @@ export interface CommentItemProps { memberId?: number; participationId?: number; createdAt?: string; + children?: CommentItemProps[]; } export interface Participant { @@ -21,6 +22,7 @@ export interface Participant { interface CommentItemUIProps extends CommentItemProps { isOwner?: boolean; participants?: Participant[]; + onReply?: (commentId: number, nickname?: string) => void; onChangeApproval?: ( participationId: number, status: Exclude, @@ -32,7 +34,11 @@ export function CommentItem({ description, commentType = 'USER', isOwner = false, + depth, onChangeApproval, + onReply, + commentId, + children, participationId, participants, }: CommentItemUIProps) { @@ -77,10 +83,13 @@ export function CommentItem({

{description}

- {!isSystem && ( - )}
diff --git a/src/widgets/postDetail/comment/comment.tsx b/src/widgets/postDetail/comment/comment.tsx index a889a74..de9bfb2 100644 --- a/src/widgets/postDetail/comment/comment.tsx +++ b/src/widgets/postDetail/comment/comment.tsx @@ -9,6 +9,7 @@ interface CommentProps { comments: CommentItemProps[]; participants?: Participant[]; isOwner?: boolean; + onReply?: (commentId: number, nickname?: string) => void; // 추가 onChangeApproval?: ( participationId: number, status: Exclude, @@ -19,6 +20,7 @@ export function Comment({ comments, participants, isOwner, + onReply, onChangeApproval, }: CommentProps) { const systemRoot = comments.filter( @@ -40,23 +42,24 @@ export function Comment({ {...comment} participants={participants} isOwner={isOwner} + onReply={onReply} onChangeApproval={onChangeApproval} /> ))} {userRoot.map((comment) => ( -
+
-
- {comments - .filter((c) => c.parentId === comment.commentId) - .map((reply) => ( + {comment.children && comment.children.length > 0 && ( +
+ {comment.children.map((reply) => ( ))} -
+
+ )}
))}