@@ -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+
2064const 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