Skip to content

Commit 0c233df

Browse files
committed
게시물/댓글 삭제기능추가
1 parent 479b9c7 commit 0c233df

2 files changed

Lines changed: 173 additions & 8 deletions

File tree

src/components/community/PostDetail.css

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,21 @@
118118
cursor: pointer;
119119
}
120120

121+
.post-util-buttons .delete-btn {
122+
background-color: #fff5f5;
123+
border-color: #f87171;
124+
color: #d61f1f;
125+
}
126+
127+
.post-util-buttons .delete-btn:hover {
128+
background-color: #fee2e2;
129+
}
130+
131+
.post-util-buttons .delete-btn:disabled {
132+
opacity: 0.6;
133+
cursor: not-allowed;
134+
}
135+
121136
/* 답변 영역 */
122137
.answer-section {
123138
margin-top: 20px;
@@ -212,6 +227,29 @@
212227
padding: 0;
213228
}
214229

230+
.comment-action-row {
231+
display: flex;
232+
gap: 10px;
233+
align-items: center;
234+
margin-top: 6px;
235+
}
236+
237+
.comment-delete-btn,
238+
.reply-delete-btn {
239+
font-size: 12px;
240+
color: #d61f1f;
241+
background: none;
242+
border: none;
243+
cursor: pointer;
244+
padding: 0;
245+
}
246+
247+
.comment-delete-btn:disabled,
248+
.reply-delete-btn:disabled {
249+
opacity: 0.6;
250+
cursor: not-allowed;
251+
}
252+
215253
/* 대댓글 입력 영역 (개선된 디자인) */
216254
.reply-form {
217255
margin: 10px 0 0 18px;
@@ -279,6 +317,12 @@
279317
padding: 4px 0;
280318
}
281319

320+
.reply-meta {
321+
font-size: 13px;
322+
color: #888;
323+
margin-bottom: 4px;
324+
}
325+
282326

283327
.empty-comment {
284328
text-align: center;

src/components/community/PostDetail.jsx

Lines changed: 129 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ export default function PostDetail() {
4848
// 댓글 작성 상태
4949
const [newComment, setNewComment] = useState("");
5050
const [posting, setPosting] = useState(false);
51+
const [deletingPost, setDeletingPost] = useState(false);
52+
const [deletingCommentId, setDeletingCommentId] = useState(null);
5153

5254
// 대댓글 작성 상태
5355
const [replyTarget, setReplyTarget] = useState(null); // 대댓글을 달 댓글 ID
@@ -58,6 +60,19 @@ export default function PostDetail() {
5860
const authHeader = tokenRaw
5961
? tokenRaw.startsWith("Bearer ") ? tokenRaw : `Bearer ${tokenRaw}`
6062
: null;
63+
const currentUserId = useMemo(() => localStorage.getItem("userId") || "", []);
64+
const currentUsername = useMemo(() => localStorage.getItem("username") || "", []);
65+
const currentRole = useMemo(() => (localStorage.getItem("role") || "").toUpperCase(), []);
66+
const hasManageRole = useMemo(() => ["ADMIN", "MANAGER", "ROLE_ADMIN", "ROLE_MANAGER"].includes(currentRole), [currentRole]);
67+
const matchesCurrentUser = useCallback((writerName, writerId) => {
68+
if (writerId && currentUserId) return String(writerId) === String(currentUserId);
69+
if (writerName && currentUsername) return writerName === currentUsername;
70+
return false;
71+
}, [currentUserId, currentUsername]);
72+
const canManageRecord = useCallback((writerName, writerId) => {
73+
if (hasManageRole) return true;
74+
return matchesCurrentUser(writerName, writerId);
75+
}, [hasManageRole, matchesCurrentUser]);
6176

6277
// 좋아요 수 및 내 상태 재조회
6378
const refreshLikeStatus = async () => {
@@ -113,7 +128,8 @@ export default function PostDetail() {
113128
id: data.id,
114129
title: data.title,
115130
content: data.content || "",
116-
author: data.writer || "익명",
131+
author: data.writer || data.author || "익명",
132+
authorId: data.writerId ?? data.authorId ?? data.userId ?? null,
117133
date: data.createdAt ? new Date(data.createdAt).toLocaleString() : "",
118134
tags: Array.isArray(data.tags) ? data.tags : [],
119135
});
@@ -176,6 +192,19 @@ export default function PostDetail() {
176192
fetchComments();
177193
}, [id, authHeader, fetchComments]);
178194

195+
const canDeletePost = useMemo(() => {
196+
if (hasManageRole) return true;
197+
if (!post) return false;
198+
return matchesCurrentUser(post.author, post.authorId);
199+
}, [hasManageRole, post, matchesCurrentUser]);
200+
201+
const canDeleteComment = useCallback((comment) => {
202+
if (!comment) return false;
203+
const writerName = comment.writer ?? comment.author ?? comment.nickname;
204+
const writerId = comment.writerId ?? comment.authorId ?? comment.userId;
205+
return canManageRecord(writerName, writerId);
206+
}, [canManageRecord]);
207+
179208

180209
// 좋아요 토글
181210
const handleToggleLike = async () => {
@@ -304,6 +333,67 @@ export default function PostDetail() {
304333
}
305334
};
306335

336+
const handleDeletePost = async () => {
337+
if (!authHeader) {
338+
promptLogin();
339+
return;
340+
}
341+
if (deletingPost) return;
342+
if (!window.confirm("게시글을 삭제하시겠습니까?")) return;
343+
344+
try {
345+
setDeletingPost(true);
346+
const res = await fetch(`${config.API_BASE_URL}/api/posts/${id}`, {
347+
method: "DELETE",
348+
headers: { Authorization: authHeader, Accept: "application/json" },
349+
});
350+
351+
if (!res.ok) {
352+
const text = await res.text();
353+
throw new Error(text || `게시글 삭제 실패 (${res.status})`);
354+
}
355+
356+
alert("게시글이 삭제되었습니다.");
357+
navigate("/community");
358+
} catch (e) {
359+
alert(e.message || "게시글 삭제에 실패했습니다.");
360+
} finally {
361+
setDeletingPost(false);
362+
}
363+
};
364+
365+
const handleDeleteComment = async (commentId) => {
366+
if (!authHeader) {
367+
promptLogin();
368+
return;
369+
}
370+
if (!commentId || deletingCommentId === commentId) return;
371+
if (!window.confirm("삭제하시겠습니까?")) return;
372+
373+
try {
374+
setDeletingCommentId(commentId);
375+
const res = await fetch(`${config.API_BASE_URL}/api/comments/${commentId}`, {
376+
method: "DELETE",
377+
headers: { Authorization: authHeader, Accept: "application/json" },
378+
});
379+
380+
if (!res.ok) {
381+
const text = await res.text();
382+
throw new Error(text || `댓글 삭제 실패 (${res.status})`);
383+
}
384+
385+
if (replyTarget === commentId) {
386+
setReplyTarget(null);
387+
setReplyContent("");
388+
}
389+
await fetchComments();
390+
} catch (e) {
391+
alert(e.message || "댓글 삭제에 실패했습니다.");
392+
} finally {
393+
setDeletingCommentId(null);
394+
}
395+
};
396+
307397

308398
if (loadingPost) {
309399
return <div className="post-detail-container"><div className="post-detail-left"><p>게시글을 불러오는 중입니다…</p></div></div>;
@@ -361,6 +451,15 @@ export default function PostDetail() {
361451
>
362452
🔗
363453
</button>
454+
{canDeletePost && (
455+
<button
456+
className="delete-btn"
457+
onClick={handleDeletePost}
458+
disabled={deletingPost}
459+
>
460+
{deletingPost ? "삭제 중…" : "삭제"}
461+
</button>
462+
)}
364463
</div>
365464

366465
<div className="section-divider" />
@@ -420,12 +519,23 @@ export default function PostDetail() {
420519
</div>
421520

422521
{/* 답글 버튼 */}
423-
<button
424-
className="reply-toggle-btn" // ✅ 클래스 적용
425-
onClick={() => setReplyTarget(c.id === replyTarget ? null : c.id)}
426-
>
427-
답글
428-
</button>
522+
<div className="comment-action-row">
523+
<button
524+
className="reply-toggle-btn"
525+
onClick={() => setReplyTarget(c.id === replyTarget ? null : c.id)}
526+
>
527+
답글
528+
</button>
529+
{canDeleteComment(c) && (
530+
<button
531+
className="comment-delete-btn"
532+
onClick={() => handleDeleteComment(c.id)}
533+
disabled={deletingCommentId === c.id}
534+
>
535+
{deletingCommentId === c.id ? "삭제 중…" : "삭제"}
536+
</button>
537+
)}
538+
</div>
429539

430540
{/* 대댓글 입력창 */}
431541
{replyTarget === c.id && (
@@ -460,8 +570,19 @@ export default function PostDetail() {
460570
<ul className="reply-list"> {/* ✅ 클래스 적용 */}
461571
{c.replies.map((r) => (
462572
<li key={r.id} className="reply-item"> {/* ✅ 클래스 적용 */}
463-
<b>{r.writer || "익명"}</b> · {r.createdAt ? new Date(r.createdAt).toLocaleString() : ""}
573+
<div className="reply-meta">
574+
<b>{r.writer || "익명"}</b> · {r.createdAt ? new Date(r.createdAt).toLocaleString() : ""}
575+
</div>
464576
<div>{r.content}</div>
577+
{canDeleteComment(r) && (
578+
<button
579+
className="reply-delete-btn"
580+
onClick={() => handleDeleteComment(r.id)}
581+
disabled={deletingCommentId === r.id}
582+
>
583+
{deletingCommentId === r.id ? "삭제 중…" : "삭제"}
584+
</button>
585+
)}
465586
</li>
466587
))}
467588
</ul>

0 commit comments

Comments
 (0)