Skip to content

Commit f8660bb

Browse files
authored
Merge pull request #157 from Gachon-Univ-Creative-Code-Innovation/feat/GUC-245-community-comment-api
Feat/guc 245 community comment api
2 parents 61f14a7 + 97d22bd commit f8660bb

File tree

2 files changed

+214
-88
lines changed

2 files changed

+214
-88
lines changed

src/screens/CommunityViewPost/CommunityViewPost.css

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,26 @@
183183
/* border-top: 1px solid #eee; */
184184
}
185185

186+
187+
.comment-profile-wrapper {
188+
align-self: stretch;
189+
background-color: transparent;
190+
overflow: hidden; /*컨테이너 범위 벗어나면 숨김 */
191+
position: relative;
192+
width: 36px;
193+
height: 36px;
194+
border-radius: 50%;
195+
margin-top: 4px;
196+
flex-shrink: 0;
197+
}
198+
199+
.comment-profile-img {
200+
width: 100%;
201+
height: 100%;
202+
object-fit: cover; /* cover: 박스를 가득 채우면서 비율 유지, 잘리는 부분 발생 */
203+
object-position: center;
204+
}
205+
186206
.comment-input-wrapper {
187207
position: relative;
188208
width: 100%;

src/screens/CommunityViewPost/CommunityViewPost.jsx

Lines changed: 194 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,50 @@ function getLabelByKey(key) {
1717
}
1818

1919

20+
/**
21+
* 백엔드에서 내려준 flat list를 nested structure({ replies: [] })로 바꿔준다.
22+
* @param {Array<Object>} flatComments
23+
* └ 백엔드 GetComment DTO 배열. 각 항목에 commentId, parentCommentId, authorNickname, content, createTime 등이 있음.
24+
* @returns {Array<Object>} nestedComments
25+
*/
26+
function buildNestedComments(flatComments) {
27+
// 1) 모든 댓글을 id → 새로운 객체(프론트용)로 매핑
28+
const map = {};
29+
flatComments.forEach((c) => {
30+
map[c.commentId] = {
31+
id: c.commentId,
32+
author: c.authorNickname,
33+
text: c.content,
34+
authorId: c.authorId, // 댓글 작성자의 ID
35+
authorProfileUrl: c.authorProfileUrl, // 댓글 작성자의 프로필 이미지 URL
36+
isDeleted : c.isDeleted,
37+
// createTime(예: "2025-06-03T05:00:00")을 "2025.06.03" 형태로 포맷
38+
date: c.createTime.slice(0, 10).replace(/-/g, "."),
39+
replies: []
40+
};
41+
});
42+
43+
// 2) parentCommentId가 있으면, 해당 parent의 replies 배열에 push
44+
// 없으면 최상위(root) 댓글 목록에 추가
45+
const nested = [];
46+
flatComments.forEach((c) => {
47+
const node = map[c.commentId];
48+
if (c.parentCommentId) {
49+
const parentNode = map[c.parentCommentId];
50+
if (parentNode) {
51+
parentNode.replies.push(node);
52+
}
53+
// 만약 parentNode가 없다면 (비정상 케이스) 그냥 무시해도 됩니다.
54+
} else {
55+
nested.push(node);
56+
}
57+
});
58+
59+
return nested;
60+
}
61+
62+
63+
2064
const CommunityViewPost = () => {
2165
const navigate = useNavigate();
2266
const { postId } = useParams();
@@ -178,61 +222,107 @@ const CommunityViewPost = () => {
178222
}, [editReplyId]);
179223

180224
// 댓글 등록
181-
const handleAddComment = () => {
225+
const handleAddComment = async() => {
182226
if (!commentValue.trim()) return;
183-
setComments([
184-
...comments,
185-
{
186-
id: Date.now(),
187-
author: myName,
188-
text: commentValue,
189-
date: new Date().toISOString().slice(0, 10).replace(/-/g, '.'),
190-
replies: [],
191-
},
192-
]);
193-
setCommentValue("");
227+
try {
228+
const token = localStorage.getItem("jwtToken");
229+
const payload = {
230+
postId: Number(postId), // 현재 보고 있는 글의 ID
231+
parentCommentId: null, // 루트 댓글이므로 null
232+
content: commentValue.trim(), // 입력된 댓글 내용
233+
};
234+
235+
await api.post(
236+
"/blog-service/comments",
237+
payload,
238+
{ headers: { Authorization: `Bearer ${token}` } }
239+
);
240+
241+
setCommentValue("");
242+
fetchComments();
243+
} catch (e) {
244+
console.error("댓글 등록 실패", e);
245+
}
194246
};
195247

196248
// 답글 등록
197-
const handleAddReply = (commentId) => {
249+
const handleAddReply = async(commentId) => {
198250
if (!replyValue.trim()) return;
199-
setComments(comments.map(comment =>
200-
comment.id === commentId
201-
? {
202-
...comment,
203-
replies: [
204-
...(comment.replies || []),
205-
{
206-
id: Date.now(),
207-
author: myName,
208-
text: replyValue,
209-
date: new Date().toISOString().slice(0, 10).replace(/-/g, '.'),
210-
},
211-
],
212-
}
213-
: comment
214-
));
215-
setReplyValue("");
216-
setReplyTo(null);
251+
252+
try {
253+
const token = localStorage.getItem("jwtToken");
254+
const payload = {
255+
postId: Number(postId), // 현재 보고 있는 게시글 ID
256+
parentCommentId: commentId, // 답글을 다는 부모 댓글 ID
257+
content: replyValue.trim(), // 입력된 답글 내용
258+
};
259+
260+
await api.post(
261+
"/blog-service/comments",
262+
payload,
263+
{ headers: { Authorization: `Bearer ${token}` } }
264+
);
265+
266+
setReplyValue("");
267+
setReplyTo(null);
268+
fetchComments();
269+
} catch (e) {
270+
console.error("답글 등록 실패", e);
271+
}
272+
};
273+
274+
//댓글 조회
275+
const fetchComments = async () => {
276+
try {
277+
const token = localStorage.getItem("jwtToken");
278+
// “postId별 댓글 조회” API 호출
279+
const res = await api.get(
280+
`/blog-service/comments/${postId}`,
281+
{ headers: { Authorization: `Bearer ${token}` } }
282+
);
283+
const flatList = res.data.data.commentList;
284+
console.log("댓글 데이터:", flatList);
285+
const nested = buildNestedComments(flatList);
286+
setComments(nested);
287+
} catch (err) {
288+
console.error("댓글 조회 실패:", err);
289+
}
217290
};
218291

292+
// 댓글 조회
293+
useEffect(() => {
294+
fetchComments();
295+
}, [postId]);
296+
219297
// 댓글 삭제
220-
const handleDeleteComment = (commentId) => {
221-
setComments(comments.filter(comment => comment.id !== commentId));
222-
setOpenMenuId(null);
298+
const handleDeleteComment = async(commentId) => {
299+
try {
300+
const token = localStorage.getItem("jwtToken");
301+
302+
await api.delete(`/blog-service/comments/${commentId}`, {
303+
headers: { Authorization: `Bearer ${token}` }
304+
});
305+
306+
setOpenMenuId(null);
307+
fetchComments();
308+
} catch (e) {
309+
console.error("댓글 삭제 실패", e);
310+
}
223311
};
224312

225313
// 답글 삭제
226-
const handleDeleteReply = (commentId, replyId) => {
227-
setComments(comments.map(comment =>
228-
comment.id === commentId
229-
? {
230-
...comment,
231-
replies: comment.replies.filter(reply => reply.id !== replyId)
232-
}
233-
: comment
234-
));
235-
setOpenMenuId(null);
314+
const handleDeleteReply = async(commentId, replyId) => {
315+
try {
316+
const token = localStorage.getItem("jwtToken");
317+
318+
await api.delete(`/blog-service/comments/${replyId}`, {
319+
headers: { Authorization: `Bearer ${token}` }
320+
});
321+
setOpenMenuId(null);
322+
fetchComments();
323+
} catch (e) {
324+
console.error("답글 삭제 실패", e);
325+
}
236326
};
237327

238328
// 댓글 수정 모드 진입
@@ -253,54 +343,62 @@ const CommunityViewPost = () => {
253343
};
254344

255345
// 댓글 수정 저장
256-
const handleSaveEditComment = (commentId) => {
346+
const handleSaveEditComment = async(commentId) => {
257347
if (!editCommentValue.trim()) return;
258-
setComments(comments.map(comment =>
259-
comment.id === commentId
260-
? { ...comment, text: editCommentValue }
261-
: comment
262-
));
263-
setEditCommentId(null);
264-
setEditCommentValue("");
348+
349+
try {
350+
const token = localStorage.getItem("jwtToken");
351+
352+
await api.patch(
353+
`/blog-service/comments/${commentId}`,
354+
{ content: editCommentValue },
355+
{ headers: { Authorization: `Bearer ${token}` } }
356+
);
357+
setEditCommentId(null);
358+
setEditCommentValue("");
359+
fetchComments();
360+
} catch (e) {
361+
console.error("댓글 수정 실패", e);
362+
}
265363
};
266364

267365
// 답글 수정 저장
268-
const handleSaveEditReply = (commentId, replyId) => {
366+
const handleSaveEditReply = async(commentId, replyId) => {
269367
if (!editReplyValue.trim()) return;
270-
setComments(comments.map(comment =>
271-
comment.id === commentId
272-
? {
273-
...comment,
274-
replies: comment.replies.map(reply =>
275-
reply.id === replyId
276-
? { ...reply, text: editReplyValue }
277-
: reply
278-
)
279-
}
280-
: comment
281-
));
282-
setEditReplyId(null);
283-
setEditReplyValue("");
368+
try {
369+
const token = localStorage.getItem("jwtToken");
370+
371+
await api.patch(
372+
`/blog-service/comments/${replyId}`,
373+
{ content: editReplyValue },
374+
{ headers: { Authorization: `Bearer ${token}` } }
375+
);
376+
setEditReplyId(null);
377+
setEditReplyValue("");
378+
fetchComments();
379+
} catch (e) {
380+
console.error("답글 수정 실패", e);
381+
}
284382
};
285383

286384
// 글 수정 버튼 클릭 - Write 페이지로 이동
287-
const handleEditPost = () => {
288-
console.log("navigate state:", { editMode: true, post });
289-
290-
navigate('/community/write', {
291-
state: {
292-
editMode: true,
293-
postData: {
294-
title: post.title,
295-
category: post.category,
296-
content: post.content,
297-
tags: post.tag,
298-
id: post.id
299-
}
300-
}
301-
});
302-
setOpenMenuId(null);
303-
};
385+
// const handleEditPost = () => {
386+
// console.log("navigate state:", { editMode: true, post });
387+
388+
// navigate('/community/write', {
389+
// state: {
390+
// editMode: true,
391+
// postData: {
392+
// title: post.title,
393+
// category: post.category,
394+
// content: post.content,
395+
// tags: post.tag,
396+
// id: post.id
397+
// }
398+
// }
399+
// });
400+
// setOpenMenuId(null);
401+
// };
304402

305403
// 글 삭제 버튼 클릭
306404
const handleDeletePost = async() => {
@@ -478,7 +576,11 @@ if (!post) {
478576
<div className="comment-list">
479577
{comments.map((comment) => (
480578
<div key={comment.id} className="comment-item">
481-
<div className="comment-profile"></div>
579+
<div className="comment-profile-wrapper">
580+
{comment.authorProfileUrl && (
581+
<img src={comment.authorProfileUrl} alt="comment" className="comment-profile-img" />
582+
)}
583+
</div>
482584
<div className="comment-content-block">
483585
<div className="comment-author">{comment.author}</div>
484586
{editCommentId === comment.id ? (
@@ -499,13 +601,13 @@ if (!post) {
499601
)}
500602
<div className="comment-meta">
501603
<span>{comment.date}</span>
502-
<span
604+
{!comment.isDeleted && (<span
503605
className="reply-btn"
504606
style={{ cursor: "pointer", color: "#6c6c8a", marginLeft: 8 }}
505607
onClick={() => setReplyTo(replyTo === comment.id ? null : comment.id)}
506608
>
507609
reply
508-
</span>
610+
</span>)}
509611
</div>
510612
{/* 답글 입력창 */}
511613
{replyTo === comment.id && (
@@ -528,7 +630,11 @@ if (!post) {
528630
<div className="comment-replies-list">
529631
{comment.replies.map(reply => (
530632
<div key={reply.id} className="comment-reply-item">
531-
<div className="comment-profile"></div>
633+
<div className="comment-profile-wrapper">
634+
{reply.authorProfileUrl && (
635+
<img src={reply.authorProfileUrl} alt="comment" className="comment-profile-img" />
636+
)}
637+
</div>
532638
<div className="reply-content">
533639
<div className="comment-author">{reply.author}</div>
534640
{editReplyId === reply.id ? (

0 commit comments

Comments
 (0)