Conversation
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
|
Caution Review failedThe pull request is closed. Walkthrough댓글/대댓글 타입 및 API 파라미터 변경, 피드 좋아요·삭제 API 추가 및 UI 연동, 마이페이지 프로필 조회·수정 도입, 팝업/모달 렌더 경로·스타일 조정, 닉네임 필드명 통일, 장르 라벨 데이터 변경을 포함한 코드 및 데이터 업데이트입니다. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant UI as PostFooter
participant API as postFeedLike()
User->>UI: 좋아요 클릭
UI->>API: POST /feeds/{feedId}/likes { type: !isLiked }
API-->>UI: { data: { isLiked } }
UI-->>User: 좋아요 상태 및 카운트 갱신
sequenceDiagram
actor User
participant Reply as Reply/SubReply
participant Popup as MoreMenu/Confirm
participant API as deleteComment()
participant Toast as Snackbar
User->>Reply: 더보기 클릭
Reply->>Popup: openMoreMenu
User->>Popup: 삭제 선택
Popup->>Popup: openConfirm
User->>Popup: 확인
Popup->>API: DELETE /comments/{commentId}
API-->>Popup: 성공 응답
Popup->>Toast: "댓글이 삭제되었습니다."
Popup->>Reply: onDelete 또는 리로드 트리거
sequenceDiagram
actor User
participant Page as FeedDetailPage
participant API1 as submitComment()
participant API2 as getComments(postType: FEED)
User->>Page: 댓글 전송
Page->>API1: POST /comments { postId, postType: 'FEED', ... }
API1-->>Page: 성공
Page->>API2: GET /comments?postType=FEED&postId=...
API2-->>Page: 최신 댓글 반환
Page-->>User: 댓글 목록 갱신
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (2)
✨ Finishing Touches
🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 13
🔭 Outside diff range comments (1)
src/components/common/Modal/ReplyModal.tsx (1)
79-87: 내 댓글/타인 댓글 액션이 뒤바뀌어 표시되는 버그주석(“내 댓글이면 삭제하기, 다른 사람 댓글이면 신고하기”)과 달리 현재 구현은 반대로 동작합니다. 아래처럼 조건 분기를 교체하세요.
- {isMyReply ? ( - <OptionItem className="report"> - <div className="option-text">신고하기</div> - </OptionItem> - ) : ( - <OptionItem onClick={handleDelete} className="delete"> - <div className="option-text">삭제하기</div> - </OptionItem> - )} + {isMyReply ? ( + <OptionItem onClick={handleDelete} className="delete"> + <div className="option-text">삭제하기</div> + </OptionItem> + ) : ( + <OptionItem className="report"> + <div className="option-text">신고하기</div> + </OptionItem> + )}
🧹 Nitpick comments (13)
src/components/common/Post/PostFooter.tsx (1)
74-88: UX 보강 제안: 401 등 인증 오류 시 가이드 제공 및 console 로그 최소화
- 401 응답 시 로그인 유도로 전환하거나 토스트 노출이 좋습니다.
- 성공/실패를 console.log/console.error로만 남기기보다 프로젝트 공통 알림(토스트/스낵바)이 있으면 그것을 사용해 주세요.
src/api/feeds/postFeedLike.ts (2)
17-22: 반환 타입 명시로 API 래퍼 시그니처를 고정하세요명시적 반환 타입을 선언하면 호출부에서 안전한 타입 추론이 가능합니다. 현재 구현과 동작 동일합니다.
-export const postFeedLike = async (feedId: number, type: boolean) => { +export const postFeedLike = async ( + feedId: number, + type: boolean +): Promise<PostFeedLikeResponse> => { const response = await apiClient.post<PostFeedLikeResponse>(`/feeds/${feedId}/likes`, { type, }); return response.data; };
17-22: 파라미터 의미 명확화 제안boolean 파라미터 이름이 type이면 의미 파악이 어렵습니다. 서버 스펙상 body 키는 유지하되, 함수 인자명만 의미 있는 이름으로 변경하는 것을 제안합니다. 호출부 변경 영향은 최소입니다.
-export const postFeedLike = async ( - feedId: number, - type: boolean -): Promise<PostFeedLikeResponse> => { +export const postFeedLike = async ( + feedId: number, + like: boolean +): Promise<PostFeedLikeResponse> => { const response = await apiClient.post<PostFeedLikeResponse>(`/feeds/${feedId}/likes`, { - type, + type: like, // 서버 요청 스펙 유지 }); return response.data; };호출부(PostFooter)도 같이 변경:
-const response = await postFeedLike(feedId, !prevLiked); +const response = await postFeedLike(feedId, !prevLiked);(위처럼 인자명만 바뀌므로 실제 호출 코드는 동일하게 동작합니다.)
src/components/common/Modal/ReplyModal.tsx (1)
15-18: 변수 이름 섀도잉(nit): 혼동 방지용 리네이밍 권장로컬스토리지에서 읽은 userId가 파라미터 userId를 가립니다. 의미를 분리하면 가독성이 좋아집니다.
- const userId = localStorage.getItem('userId'); - if (userId) { - setCurrentUserId(Number(userId)); + const storedUserId = localStorage.getItem('userId'); + if (storedUserId) { + setCurrentUserId(Number(storedUserId)); }src/pages/mypage/Mypage.tsx (2)
30-32: 프로필 데이터를 state로 전달하는 방식을 검토해주세요.현재
navigate('/mypage/edit', { state: { profile } })로 프로필 데이터를 전달하고 있는데, 브라우저 새로고침 시 state가 사라질 수 있습니다. 편집 페이지에서 직접 API를 호출하는 방식도 고려해볼 만합니다.
34-36: 로딩 상태 처리가 너무 간단합니다.현재 "로딩 중..." 텍스트만 표시하고 있는데, 사용자 경험을 위해 적절한 스켈레톤 UI나 스피너 컴포넌트를 사용하는 것을 권장합니다.
src/api/users/getMyProfile.ts (1)
14-17: 에러 처리 로직 추가를 고려해주세요.현재 API 호출 실패 시 에러가 그대로 전파되는데, 사용자에게 친화적인 에러 메시지를 제공하거나 기본값 처리를 위해 try-catch 블록 추가를 고려해보세요.
export const getMyProfile = async () => { + try { const response = await apiClient.get<GetMyProfileResponse>('/users/my-page'); return response.data.data; + } catch (error) { + console.error('프로필 조회 실패:', error); + throw error; // 또는 기본값 반환 + } };src/components/common/Modal/MoreMenu.tsx (1)
52-52: z-index 값 증가 이유를 문서화해주세요.z-index가 1001에서 1201로 증가했는데, 다른 컴포넌트들과의 레이어 충돌 때문인지 이유를 코멘트로 문서화하는 것을 권장합니다.
src/api/users/patchProfile.ts (1)
22-25: Axios 에러 타입 처리를 개선할 수 있습니다.현재 수동으로 타입 단언을 하고 있는데,
axios의AxiosError타입을 사용하면 더 안전한 타입 처리가 가능합니다.+import { AxiosError } from 'axios'; - if (error && typeof error === 'object' && 'response' in error) { - const axiosError = error as { response?: { data?: { code?: number; message?: string } } }; + if (error instanceof AxiosError) { + const axiosError = error as AxiosError<{ code?: number; message?: string }>; if (axiosError.response?.data) {src/pages/mypage/EditPage.tsx (2)
41-44: 디버깅용 console.log 제거 필요프로덕션 코드에 디버깅용 console.log가 남아있습니다.
- // nicknameError 상태 변경 추적 - useEffect(() => { - console.log('nicknameError 상태가 변경됨:', nicknameError); - }, [nicknameError]);
52-60: 주석 처리된 유효성 검사 코드 정리 필요클라이언트 측 유효성 검사가 주석 처리되어 있습니다. 즉각적인 피드백을 위해 클라이언트 측 검증을 활성화하거나, 사용하지 않는다면 코드를 완전히 제거하세요.
클라이언트 측 검증을 활성화하여 사용자 경험을 개선하는 것을 권장합니다:
const onRightClick = async () => { - // 닉네임 유효성 검사 - // if (nickname.trim() === '') { - // setNicknameError('변경할 닉네임을 입력해주세요.'); - // return; - // } - - // if (nickname === currentNickname) { - // setNicknameError('현재 닉네임과 같은 닉네임이에요.'); - // return; - // } + // 클라이언트 측 유효성 검사 + if (!nickname || nickname.trim() === '') { + setNicknameError('변경할 닉네임을 입력해주세요.'); + return; + } + + if (nickname === currentNickname) { + setNicknameError('현재 닉네임과 같은 닉네임이에요.'); + return; + }src/components/common/Post/ReplyList.tsx (1)
19-26: 키 생성 로직 개선 가능
commentId가 없을 경우 인덱스를 폴백으로 사용하는 것은 리렌더링 문제를 일으킬 수 있습니다.commentId가 항상 존재하도록 데이터 검증을 강화하는 것이 좋습니다.commentList.map((comment, commentIndex) => ( - <div className="comment-group" key={comment.commentId || `comment-${commentIndex}`}> + <div className="comment-group" key={`comment-${comment.commentId}`}> <Reply {...comment} onDelete={onReload} /> {comment.replyList.map((sub, replyIndex) => ( <SubReply - key={sub.commentId || `reply-${comment.commentId || commentIndex}-${replyIndex}`} + key={`reply-${sub.commentId}`} {...sub} onDelete={onReload} /> ))} </div> ))데이터 검증 추가:
// 컴포넌트 상단에 추가 if (commentList.some(c => !c.commentId || c.replyList.some(r => !r.commentId))) { console.error('댓글 ID가 누락된 데이터가 있습니다'); }src/components/common/Post/SubReply.tsx (1)
57-72: 사용하지 않는 주석 코드 제거 필요이전 구현으로 보이는 주석 처리된 코드가 남아있습니다. 더 이상 필요하지 않다면 제거하세요.
- // const handleMoreClick = () => { - // if (containerRef.current) { - // const rect = containerRef.current.getBoundingClientRect(); - // openReplyModal({ - // isOpen: true, - // userId: creatorId, - // commentId: commentId, - // position: { - // x: rect.right, - // y: rect.bottom, - // }, - // onClose: closePopup, - // }); - // } - // };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (23)
public/genres.json(1 hunks)src/api/comments/getComments.ts(4 hunks)src/api/feeds/deleteFeedPost.ts(1 hunks)src/api/feeds/postFeedLike.ts(1 hunks)src/api/users/getMyProfile.ts(1 hunks)src/api/users/patchProfile.ts(1 hunks)src/components/common/Modal/MoreMenu.tsx(2 hunks)src/components/common/Modal/PopupContainer.tsx(1 hunks)src/components/common/Modal/ReplyModal.tsx(2 hunks)src/components/common/NavBar.tsx(1 hunks)src/components/common/Post/PostFooter.tsx(2 hunks)src/components/common/Post/Reply.tsx(5 hunks)src/components/common/Post/ReplyList.tsx(1 hunks)src/components/common/Post/SubReply.tsx(5 hunks)src/components/today-words/MessageInput.tsx(3 hunks)src/hooks/useReplyActions.ts(2 hunks)src/pages/feed/Feed.tsx(2 hunks)src/pages/feed/FeedDetailPage.tsx(6 hunks)src/pages/mypage/EditPage.tsx(2 hunks)src/pages/mypage/Mypage.tsx(2 hunks)src/stores/usePopupStore.ts(1 hunks)src/stores/useReplyStore.ts(1 hunks)src/types/post.ts(3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (14)
src/api/users/getMyProfile.ts (1)
src/api/index.ts (1)
apiClient(27-33)
src/api/feeds/postFeedLike.ts (1)
src/api/index.ts (1)
apiClient(27-33)
src/api/feeds/deleteFeedPost.ts (1)
src/api/index.ts (1)
apiClient(27-33)
src/components/common/Modal/PopupContainer.tsx (1)
src/stores/usePopupStore.ts (1)
MoreMenuProps(14-18)
src/components/common/Modal/ReplyModal.tsx (2)
src/stores/usePopupStore.ts (1)
ReplyModalProps(28-37)src/api/comments/deleteComment.ts (1)
deleteComment(16-21)
src/api/users/patchProfile.ts (1)
src/api/index.ts (1)
apiClient(27-33)
src/pages/mypage/EditPage.tsx (4)
src/api/users/getMyProfile.ts (1)
GetMyProfileResponse(3-12)src/types/genre.ts (1)
Genre(1-7)src/api/users/patchProfile.ts (1)
patchProfile(14-50)src/pages/mypage/EditPage.styled.ts (1)
CharCount(218-225)
src/components/common/Post/PostFooter.tsx (1)
src/api/feeds/postFeedLike.ts (1)
postFeedLike(17-22)
src/components/common/Post/Reply.tsx (5)
src/api/comments/getComments.ts (1)
CommentData(3-16)src/hooks/useReplyActions.ts (1)
useReplyActions(10-54)src/hooks/usePopupActions.ts (1)
usePopupActions(9-35)src/api/comments/deleteComment.ts (1)
deleteComment(16-21)src/styles/global/global.ts (2)
colors(4-53)typography(56-77)
src/components/common/Post/ReplyList.tsx (2)
src/types/post.ts (1)
ReplyListProps(72-74)src/api/comments/getComments.ts (1)
CommentData(3-16)
src/components/today-words/MessageInput.tsx (1)
src/components/today-words/MessageInput.styled.ts (1)
ReplyContainer(32-60)
src/components/common/Post/SubReply.tsx (6)
src/api/comments/getComments.ts (1)
ReplyData(18-31)src/hooks/useReplyActions.ts (1)
useReplyActions(10-54)src/hooks/usePopupActions.ts (1)
usePopupActions(9-35)src/api/comments/postLike.ts (1)
postLike(17-22)src/api/comments/deleteComment.ts (1)
deleteComment(16-21)src/styles/global/global.ts (1)
colors(4-53)
src/pages/mypage/Mypage.tsx (2)
src/api/users/getMyProfile.ts (2)
GetMyProfileResponse(3-12)getMyProfile(14-17)src/hooks/usePopupActions.ts (1)
usePopupActions(9-35)
src/pages/feed/FeedDetailPage.tsx (4)
src/hooks/useReplyActions.ts (1)
useReplyActions(10-54)src/stores/useReplyStore.ts (1)
useReplyStore(15-28)src/api/comments/getComments.ts (1)
getComments(50-68)src/api/feeds/deleteFeedPost.ts (1)
deleteFeedPost(10-13)
🔇 Additional comments (19)
src/components/common/Post/PostFooter.tsx (1)
11-11: 좋아요 API import 연동 적절새 API 모듈 의존성 추가 적절합니다. 컴포넌트 책임과도 맞습니다.
public/genres.json (1)
11-11: 데이터 라벨 변경 이상 없음표시 라벨만 변경되며 구조/키에는 변화가 없습니다. subTitle("과학자")를 기준으로 자동 선택한다면 동작에 영향 없습니다.
src/stores/useReplyStore.ts (1)
5-5: 닉네임 기반 상태 일관성 및 레거시 식별자 제거 확인 완료
src/stores/useReplyStore.ts의 관련 라인(5, 9, 17, 21, 23)에서 모두nickname으로 통일rg -n 'targetUserName'실행 결과 레거시 식별자 미발견변경 사항 승인합니다.
src/hooks/useReplyActions.ts (1)
13-13: 스토어 변경사항과의 정합성 확보(LGTM)nickname으로의 전파가 완료되었습니다. 현재 훅 내부에서 nickname을 직접 사용하지는 않지만, 호출부에서의 표시/프롬프트 용도로 노출되는 설계라면 충분합니다.
Also applies to: 47-47
src/components/common/Modal/PopupContainer.tsx (1)
41-41: MoreMenu 오버레이/레이어링 구현 확인 완료
MoreMenu.tsx를 살펴본 결과, 아래와 같이 백드롭·포지셔닝·z-index·배경 클릭 처리 등이 모두 자체적으로 구현되어 있습니다. PopupContainer에서 래퍼를 제거해도 시각적·상호작용 레이어에는 문제가 없습니다.
- 파일:
src/components/common/Modal/MoreMenu.tsx
- Overlay
- position: fixed (전체 화면 커버)
- background-color: rgba(18,18,18,0.1), backdrop-filter: blur(2.5px)
- z-index: 1200 (≥1000)
- onClick → onClose (배경 클릭 시 닫힘)
- Container
- position: fixed, z-index: 1201 (Overlay 위)
- 클릭 이벤트 전파 차단(e.stopPropagation())
resolve_review_comment
src/components/common/Modal/ReplyModal.tsx (1)
8-8: commentId로의 치환 및 삭제 호출 정합성(LGTM)댓글 삭제 API와 로그 모두 commentId를 사용하도록 정리되어 일관성이 좋아졌습니다.
Also applies to: 36-38
src/api/feeds/deleteFeedPost.ts (1)
1-14: LGTM! 피드 삭제 API 구현이 깔끔합니다.API 응답 타입이 명확히 정의되어 있고, 함수 구현도 간결합니다. 삭제 API의 경우 data가 null인 것이 적절합니다.
src/components/today-words/MessageInput.tsx (1)
21-21: 네이밍 일관성 개선이 좋습니다.
targetUserName에서nickname으로 변경하여 코드베이스 전체의 네이밍 일관성을 개선했습니다. 변경사항이 props, 컴포넌트 시그니처, UI 로직에 일관되게 적용되었습니다.Also applies to: 31-31, 64-64, 69-69
src/types/post.ts (2)
51-51: 댓글 시스템 개선을 위한 필드 추가가 적절합니다.
replyId추가로 답글 식별이 명확해집니다.isDeleted필드로 삭제된 댓글 처리가 가능해집니다.이러한 변경사항이 댓글 관리 기능 개선에 도움이 될 것 같습니다.
Also applies to: 67-67
42-42: parentCommentCreatorNickname은 항상 제공되므로 required 변경이 적절합니다.API 응답과 스토어, 컴포넌트에서 모두 non-null string으로 사용되고 있어 옵셔널이 아닙니다:
- src/api/comments/getComments.ts: 응답에
parentCommentCreatorNickname: string포함- src/stores/useReplyStore.ts:
startReply(parentCommentCreatorNickname: string, …)- src/components/common/Post/SubReply.tsx: prop으로 받아 바로 렌더링
따라서 해당 필드를 required로 유지해도 무방합니다.
src/pages/mypage/Mypage.tsx (1)
14-15: 실제 API 연동 구현이 잘 되었습니다.mock 데이터에서 실제 API 연동으로 변경하고, 적절한 타입 정의와 함께 profile state를 관리하고 있습니다. useEffect를 통한 데이터 페칭 로직도 깔끔합니다.
Also applies to: 18-18, 22-28
src/api/users/getMyProfile.ts (1)
3-12: API 응답 타입 정의가 명확합니다.인터페이스가 잘 정의되어 있고, nested data 구조를 적절히 타입화했습니다.
src/components/common/Modal/MoreMenu.tsx (1)
26-34: 모달 오버레이 스타일 개선이 좋습니다.기존 전체 화면 오버레이에서 중앙 정렬 + 블러 효과가 추가된 방식으로 변경되어 사용자 경험이 개선되었습니다. 반응형 제약 조건도 적절히 설정되었습니다.
src/api/users/patchProfile.ts (2)
3-6: PatchProfileRequest에서 nickname이 nullable인 이유를 확인해주세요.
nickname: string | null로 정의되어 있는데, null 값으로 업데이트하는 경우가 있는지 확인이 필요합니다. 일반적으로는 닉네임 업데이트 또는 변경 없음을 구분하기 위한 것 같은데, API 스펙과 일치하는지 검토해주세요.
28-40: 에러 코드별 메시지 매핑이 잘 구현되었습니다.사용자 친화적인 한국어 메시지로 매핑되어 있고, 각 에러 상황에 맞는 적절한 안내 문구가 제공됩니다. 특히 닉네임 변경 제한사항들이 명확히 안내되고 있습니다.
src/api/comments/getComments.ts (4)
14-14: 댓글 삭제 상태 관리를 위한 isDeleted 필드 추가가 적절합니다.삭제된 댓글을 소프트 삭제 방식으로 처리할 수 있게 되어, 댓글 스레드 구조를 유지하면서 삭제 상태를 관리할 수 있습니다.
Also applies to: 30-30
47-47: postType 파라미터 필수화가 API 설계를 개선합니다.
postType을 필수 파라미터로 만들어 FEED/RECORD/VOTE 타입을 명확히 구분할 수 있게 되었습니다. 함수 시그니처도 이에 맞게 params를 필수로 변경한 것이 일관성 있습니다.Also applies to: 50-50, 61-61
64-64: URL 구성 로직 단순화가 좋습니다.항상 query string을 포함하는 방식으로 단순화하여 코드가 더 명확해졌습니다. postType이 필수가 되면서 빈 query string을 걱정할 필요가 없어졌네요.
19-21: ReplyData 변경사항(UI 호환성) 검증 완료SubReply 컴포넌트에서
parentCommentCreatorNickname을 화면에 출력하고commentId로 좋아요·삭제·답글 기능이 동작하며isDeleted로 삭제된 댓글 UI를 분기 처리되고 있음이 확인되었습니다.
코드베이스 전반의replyId참조는 mock 데이터와types/post.ts정의에만 남아 있을 뿐 실제 컴포넌트 로직에는 영향을 주지 않습니다.따라서 추가 수정 없이 UI 호환성에 문제가 없습니다.
| import { apiClient } from '../index'; | ||
|
|
||
| export interface PostFeedLikeRequest { | ||
| type: boolean; | ||
| } | ||
|
|
||
| export interface PostFeedLikeResponse { | ||
| isSuccess: boolean; | ||
| code: number; | ||
| message: string; | ||
| data: { | ||
| feedId: number; | ||
| isLiked: boolean; | ||
| }; | ||
| } | ||
|
|
||
| export const postFeedLike = async (feedId: number, type: boolean) => { | ||
| const response = await apiClient.post<PostFeedLikeResponse>(`/feeds/${feedId}/likes`, { | ||
| type, | ||
| }); | ||
| return response.data; | ||
| }; |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
API 래퍼 일관성 점검 권장
프로젝트 내 다른 API 래퍼들이 response.data 전체를 반환하는지, 혹은 data.data만 반환하는지 일관성을 확인해 주세요. 혼재 시 사용처에서 접근 경로가 달라져 버그 요인이 됩니다.
다음 스크립트로 API 래퍼들의 반환 패턴을 빠르게 점검할 수 있습니다.
🏁 Script executed:
#!/bin/bash
# API 래퍼 반환 일관성 점검
echo "==== return response.data 사용 위치 ===="
rg -n "return\s+response\.data;"
echo
echo "==== return response\.data\?\.\w+ 또는 중첩 data 반환 패턴 ===="
rg -n "return\s+response\.data\.\w+;"
echo
echo "==== isSuccess / data 접근 패턴 사용처 ===="
rg -n "isSuccess|response\.data\?" src | head -n 200Length of output: 3050
API 래퍼 반환 형태 통일 필요
현재 대부분의 API 래퍼는 response.data 전체를 반환하고 있으나, 오직 getMyProfile만 중첩된 response.data.data를 반환하고 있어 사용처에서 접근 경로가 달라질 수 있습니다. 아래 파일을 점검해 반환 형태를 일관되게 맞춰 주세요.
• 파일: src/api/users/getMyProfile.ts
위치: 16줄
변경 전:
return response.data.data;변경 후 (다른 래퍼와 동일하게):
return response.data;필요에 따라 모든 래퍼가 data.data만 반환하도록 통일하는 방안도 고려 가능합니다. 일관된 반환 구조를 선택해 전체 코드베이스에서 동일하게 적용해 주세요.
🤖 Prompt for AI Agents
In src/api/feeds/postFeedLike.ts (lines 1-22) and specifically fix the
inconsistent wrapper in src/api/users/getMyProfile.ts (around line 16):
postFeedLike currently returns response.data (which matches the majority of
wrappers) but getMyProfile returns response.data.data, so change getMyProfile to
return response.data instead of response.data.data and adjust its return type if
necessary so all API wrappers consistently return response.data across the
codebase; update any callsites/types that assumed the nested shape.
| interface NavBarProps { | ||
| src?: string; | ||
| path?: string; | ||
| } | ||
|
|
||
| const NavBar = ({ src, path }: NavBarProps) => { |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Fab 렌더링 가드 및 Prop 타입 강화 제안
path만 있고 src가 없는 경우 Fab에 undefined가 전달될 수 있습니다. 최소한 렌더링 가드로 방지하고, 가능하면 타입을 더 엄격하게 해주세요.
렌더링 가드(권장, 간단):
- {path && <Fab src={src} path={path} />}
+ {path && src && <Fab src={src} path={path} />}타입을 엄격하게(선택):
-interface NavBarProps {
- src?: string;
- path?: string;
-}
+type NavBarProps = { src: string; path: string } | { src?: undefined; path?: undefined };Also applies to: 97-97
🤖 Prompt for AI Agents
In src/components/common/NavBar.tsx around lines 74-79 (and also at line 97),
the NavBar currently accepts optional src and path which can pass undefined into
the Fab component; add a rendering guard so Fab is only rendered when src is
defined (e.g., src && <Fab ...>), and strengthen the prop types by making src
required when path is present or by using a discriminated union so the component
signature prevents path-without-src (e.g., define props as either { src: string;
path?: string } | { src?: undefined; path?: undefined }). Ensure you update
usages and adjust any conditional rendering at line 97 accordingly.
| const handleLike = async () => { | ||
| try { | ||
| const response = await postFeedLike(feedId, !liked); | ||
|
|
||
| if (response.isSuccess) { | ||
| // 성공 시 상태 업데이트 | ||
| setLiked(response.data.isLiked); | ||
| setLikeCount(prev => (response.data.isLiked ? prev + 1 : prev - 1)); | ||
| console.log('좋아요 상태 변경 성공:', response.data.isLiked); | ||
| } else { | ||
| console.error('좋아요 상태 변경 실패:', response.message); | ||
| } | ||
| } catch (error) { | ||
| console.error('좋아요 API 호출 실패:', error); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
좋아요 토글 동시 클릭(중복 요청) 시 레이스/역순 응답으로 카운트가 틀어질 수 있습니다
- 여러 번 빠르게 클릭 시 요청이 겹치고, 응답이 역순으로 도착하면 likeCount가 실제 서버 상태와 달라질 수 있습니다.
- 또한 서버가 동일 상태를 반환해도 현재 로직은 isLiked가 true면 무조건 +1, false면 -1 하므로 “상태 변화가 없는 경우”에도 카운트가 변합니다.
- 음수 카운트 방어도 필요합니다.
다음과 같이 in-flight 가드와 안전한 델타 계산을 적용해 주세요.
적용 diff(해당 범위 내 수정):
- const handleLike = async () => {
- try {
- const response = await postFeedLike(feedId, !liked);
-
- if (response.isSuccess) {
- // 성공 시 상태 업데이트
- setLiked(response.data.isLiked);
- setLikeCount(prev => (response.data.isLiked ? prev + 1 : prev - 1));
- console.log('좋아요 상태 변경 성공:', response.data.isLiked);
- } else {
- console.error('좋아요 상태 변경 실패:', response.message);
- }
- } catch (error) {
- console.error('좋아요 API 호출 실패:', error);
- }
- };
+ const handleLike = async () => {
+ if (liking) return; // 중복 클릭 방지
+ setLiking(true);
+ const prevLiked = liked;
+ try {
+ const response = await postFeedLike(feedId, !prevLiked);
+
+ if (response.isSuccess) {
+ const nextLiked = response.data?.isLiked ?? !prevLiked;
+ // 상태 변화가 있는 경우에만 카운트 변경
+ setLiked(nextLiked);
+ setLikeCount(prev => {
+ if (nextLiked === prevLiked) return prev;
+ const next = nextLiked ? prev + 1 : prev - 1;
+ return Math.max(next, 0); // 음수 방어
+ });
+ console.log('좋아요 상태 변경 성공:', nextLiked);
+ } else {
+ console.error('좋아요 상태 변경 실패:', response.message);
+ }
+ } catch (error) {
+ console.error('좋아요 API 호출 실패:', error);
+ } finally {
+ setLiking(false);
+ }
+ };범위 밖 추가(상단 state 정의에 추가):
// 추가: 중복 요청 방지용 상태
const [liking, setLiking] = useState(false);🤖 Prompt for AI Agents
In src/components/common/Post/PostFooter.tsx around lines 74 to 88, add a guard
to prevent duplicate in-flight requests and compute a safe delta based on
previous state and the server-returned isLiked: define a top-level state const
[liking, setLiking] = useState(false); at the component state area, then in
handleLike return early if liking is true, setLiking(true) before the API call
and setLiking(false) in finally; after a successful response only update liked
to response.data.isLiked and update likeCount using the previous value and
comparing prevLiked (use a functional setState) to response.data.isLiked so you
only apply +1 when it changed from false→true or -1 for true→false, and ensure
the new likeCount is clamped to a minimum of 0; also avoid changing count if
server reports no state change and log errors as currently done.
| if (response.isSuccess) { | ||
| // 약간의 지연 후 스낵바 오픈 → 진입 애니메이션이 확실히 보이도록 | ||
| setTimeout(() => { | ||
| openSnackbar({ | ||
| message: '댓글이 삭제되었습니다.', | ||
| variant: 'top', | ||
| onClose: closePopup, | ||
| }); | ||
| }, 100); | ||
| if (onDelete) { | ||
| onDelete(); | ||
| } | ||
| } else { | ||
| setTimeout(() => { | ||
| openSnackbar({ | ||
| message: '댓글 삭제에 실패했습니다.', | ||
| variant: 'top', | ||
| onClose: closePopup, | ||
| }); | ||
| }, 100); | ||
| } | ||
| } catch (error) { | ||
| console.error('댓글 삭제 실패:', error); | ||
| closePopup(); | ||
| setTimeout(() => { | ||
| openSnackbar({ | ||
| message: '댓글 삭제에 실패했습니다.', | ||
| variant: 'top', | ||
| onClose: closePopup, | ||
| }); | ||
| }, 100); | ||
| } |
There was a problem hiding this comment.
setTimeout 사용 시 메모리 누수 위험
컴포넌트가 언마운트될 때 setTimeout이 정리되지 않아 메모리 누수가 발생할 수 있습니다. useEffect cleanup 또는 useRef를 사용하여 타이머를 관리하세요.
다음과 같이 수정하는 것을 제안합니다:
+ const timeoutRef = useRef<NodeJS.Timeout | null>(null);
+
+ useEffect(() => {
+ return () => {
+ if (timeoutRef.current) {
+ clearTimeout(timeoutRef.current);
+ }
+ };
+ }, []);
const handleDelete = async () => {
try {
const response = await deleteComment(commentId);
closePopup();
if (response.isSuccess) {
- setTimeout(() => {
+ timeoutRef.current = setTimeout(() => {
openSnackbar({
message: '댓글이 삭제되었습니다.',
variant: 'top',
onClose: closePopup,
});
}, 100);Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/components/common/Post/Reply.tsx around lines 59 to 90, the code uses
setTimeout to open snackbars but never clears the timers, risking memory leaks
if the component unmounts; change to store timeout IDs (useRef) for each
scheduled snackbar, clear any existing timeout before setting a new one, and add
a useEffect cleanup that calls clearTimeout on stored IDs (and resets refs) so
timers are cancelled on unmount; also ensure the timeout ID type matches the
environment (number for browser or NodeJS.Timeout for SSR) and clear the ID
references after clearing.
| setTimeout(() => { | ||
| openSnackbar({ | ||
| message: '댓글 삭제에 실패했습니다.', | ||
| variant: 'top', | ||
| onClose: closePopup, | ||
| }); | ||
| }, 100); | ||
| } | ||
| } catch (error) { | ||
| console.error('댓글 삭제 실패:', error); | ||
| closePopup(); | ||
| setTimeout(() => { | ||
| openSnackbar({ | ||
| message: '댓글 삭제에 실패했습니다.', | ||
| variant: 'top', | ||
| onClose: closePopup, | ||
| }); | ||
| }, 100); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
중복된 에러 처리 로직 개선 필요
에러 스낵바 표시 로직이 중복되어 있습니다. 공통 함수로 추출하여 코드 중복을 제거하세요.
+ const showErrorSnackbar = () => {
+ setTimeout(() => {
+ openSnackbar({
+ message: '댓글 삭제에 실패했습니다.',
+ variant: 'top',
+ onClose: closePopup,
+ });
+ }, 100);
+ };
if (response.isSuccess) {
// ... success logic
} else {
- setTimeout(() => {
- openSnackbar({
- message: '댓글 삭제에 실패했습니다.',
- variant: 'top',
- onClose: closePopup,
- });
- }, 100);
+ showErrorSnackbar();
}
} catch (error) {
console.error('댓글 삭제 실패:', error);
closePopup();
- setTimeout(() => {
- openSnackbar({
- message: '댓글 삭제에 실패했습니다.',
- variant: 'top',
- onClose: closePopup,
- });
- }, 100);
+ showErrorSnackbar();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| setTimeout(() => { | |
| openSnackbar({ | |
| message: '댓글 삭제에 실패했습니다.', | |
| variant: 'top', | |
| onClose: closePopup, | |
| }); | |
| }, 100); | |
| } | |
| } catch (error) { | |
| console.error('댓글 삭제 실패:', error); | |
| closePopup(); | |
| setTimeout(() => { | |
| openSnackbar({ | |
| message: '댓글 삭제에 실패했습니다.', | |
| variant: 'top', | |
| onClose: closePopup, | |
| }); | |
| }, 100); | |
| } | |
| // extract duplicated error-snackbar logic | |
| const showErrorSnackbar = () => { | |
| setTimeout(() => { | |
| openSnackbar({ | |
| message: '댓글 삭제에 실패했습니다.', | |
| variant: 'top', | |
| onClose: closePopup, | |
| }); | |
| }, 100); | |
| }; | |
| if (response.isSuccess) { | |
| // ... success logic | |
| } else { | |
| showErrorSnackbar(); | |
| } | |
| } catch (error) { | |
| console.error('댓글 삭제 실패:', error); | |
| closePopup(); | |
| showErrorSnackbar(); | |
| } |
🤖 Prompt for AI Agents
In src/components/common/Post/Reply.tsx around lines 72 to 90, the snackbar
error display logic is duplicated in both the if/else and catch branches;
extract a small helper (e.g., showDeleteErrorSnackbar) inside the module that
calls openSnackbar with message: '댓글 삭제에 실패했습니다.', variant: 'top', and onClose:
closePopup (and wraps the call in the 100ms setTimeout if needed), then replace
the duplicated blocks with a single call to that helper from both locations.
| if (loading) { | ||
| return ( | ||
| <Wrapper> | ||
| <TitleHeader | ||
| leftIcon={<img src={leftArrow} alt="뒤로가기" />} | ||
| onLeftClick={handleBackClick} | ||
| /> | ||
| <LoadingMessage>피드 글 로딩 중...</LoadingMessage> | ||
| </Wrapper> | ||
| ); | ||
| return <></>; | ||
| } | ||
|
|
||
| if (error) { | ||
| return <></>; | ||
| } | ||
|
|
||
| // 에러가 있을 때 | ||
| if (error || !feedData) { | ||
| return ( | ||
| <Wrapper> | ||
| <TitleHeader | ||
| leftIcon={<img src={leftArrow} alt="뒤로가기" />} | ||
| onLeftClick={handleBackClick} | ||
| /> | ||
| <ErrorMessage>{error || '피드 글을 찾을 수 없어요.'}</ErrorMessage> | ||
| </Wrapper> | ||
| ); | ||
| if (!feedData) { | ||
| return <></>; | ||
| } |
There was a problem hiding this comment.
로딩 및 에러 상태 UI 누락
로딩 중이거나 에러가 발생했을 때 빈 화면만 표시됩니다. 사용자 경험을 위해 적절한 UI를 제공하세요.
if (loading) {
- return <></>;
+ return (
+ <Wrapper>
+ <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
+ <span>로딩 중...</span>
+ </div>
+ </Wrapper>
+ );
}
if (error) {
- return <></>;
+ return (
+ <Wrapper>
+ <div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
+ <span>{error}</span>
+ <button onClick={() => navigate(-1)}>돌아가기</button>
+ </div>
+ </Wrapper>
+ );
}
if (!feedData) {
- return <></>;
+ return (
+ <Wrapper>
+ <div>피드를 찾을 수 없습니다.</div>
+ </Wrapper>
+ );
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (loading) { | |
| return ( | |
| <Wrapper> | |
| <TitleHeader | |
| leftIcon={<img src={leftArrow} alt="뒤로가기" />} | |
| onLeftClick={handleBackClick} | |
| /> | |
| <LoadingMessage>피드 글 로딩 중...</LoadingMessage> | |
| </Wrapper> | |
| ); | |
| return <></>; | |
| } | |
| if (error) { | |
| return <></>; | |
| } | |
| // 에러가 있을 때 | |
| if (error || !feedData) { | |
| return ( | |
| <Wrapper> | |
| <TitleHeader | |
| leftIcon={<img src={leftArrow} alt="뒤로가기" />} | |
| onLeftClick={handleBackClick} | |
| /> | |
| <ErrorMessage>{error || '피드 글을 찾을 수 없어요.'}</ErrorMessage> | |
| </Wrapper> | |
| ); | |
| if (!feedData) { | |
| return <></>; | |
| } | |
| if (loading) { | |
| return ( | |
| <Wrapper> | |
| <div | |
| style={{ | |
| display: 'flex', | |
| justifyContent: 'center', | |
| alignItems: 'center', | |
| height: '100vh', | |
| }} | |
| > | |
| <span>로딩 중...</span> | |
| </div> | |
| </Wrapper> | |
| ); | |
| } | |
| if (error) { | |
| return ( | |
| <Wrapper> | |
| <div | |
| style={{ | |
| display: 'flex', | |
| flexDirection: 'column', | |
| justifyContent: 'center', | |
| alignItems: 'center', | |
| height: '100vh', | |
| }} | |
| > | |
| <span>{error}</span> | |
| <button onClick={() => navigate(-1)}>돌아가기</button> | |
| </div> | |
| </Wrapper> | |
| ); | |
| } | |
| if (!feedData) { | |
| return ( | |
| <Wrapper> | |
| <div>피드를 찾을 수 없습니다.</div> | |
| </Wrapper> | |
| ); | |
| } |
🤖 Prompt for AI Agents
In src/pages/feed/FeedDetailPage.tsx around lines 137 to 147, the component
returns empty fragments for loading, error and missing feedData; replace these
with real UI: render a visible loading indicator (spinner or skeleton) when
loading is true, render a user-friendly error state when error exists (message +
optional Retry button that calls the existing refetch or navigation handler),
and render an empty-state placeholder when !feedData (e.g., "Feed not found"
with action). Ensure you reuse existing shared components (LoadingSpinner,
ErrorBanner, EmptyState) if available and keep accessibility attributes
(aria-busy/role=status) and consistent styling with the page.
| const EditPage = () => { | ||
| const location = useLocation(); | ||
| const navigate = useNavigate(); | ||
| const profile = location.state.profile as GetMyProfileResponse['data']; | ||
|
|
||
| const [nickname, setNickname] = useState<string | null>(null); | ||
| const [currentNickname] = useState(profile.nickname || ''); | ||
| const [nicknameError, setNicknameError] = useState(''); | ||
| const maxLength = 10; | ||
|
|
||
| const [genres, setGenres] = useState<Genre[]>([]); | ||
| const [selectedId, setSelectedId] = useState<string | null>(null); | ||
|
|
||
| useEffect(() => { | ||
| if (!profile) return; | ||
|
|
There was a problem hiding this comment.
location.state가 없을 경우 오류 발생 가능
사용자가 URL을 직접 입력하여 페이지에 접근하거나 새로고침할 경우 location.state가 undefined일 수 있어 앱이 충돌할 수 있습니다.
다음과 같이 수정하세요:
const EditPage = () => {
const location = useLocation();
const navigate = useNavigate();
- const profile = location.state.profile as GetMyProfileResponse['data'];
+ const profile = location.state?.profile as GetMyProfileResponse['data'] | undefined;
+
+ useEffect(() => {
+ if (!profile) {
+ // 프로필 데이터가 없으면 마이페이지로 리다이렉트
+ navigate('/mypage', { replace: true });
+ }
+ }, [profile, navigate]);
const [nickname, setNickname] = useState<string | null>(null);
- const [currentNickname] = useState(profile.nickname || '');
+ const [currentNickname] = useState(profile?.nickname || '');📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const EditPage = () => { | |
| const location = useLocation(); | |
| const navigate = useNavigate(); | |
| const profile = location.state.profile as GetMyProfileResponse['data']; | |
| const [nickname, setNickname] = useState<string | null>(null); | |
| const [currentNickname] = useState(profile.nickname || ''); | |
| const [nicknameError, setNicknameError] = useState(''); | |
| const maxLength = 10; | |
| const [genres, setGenres] = useState<Genre[]>([]); | |
| const [selectedId, setSelectedId] = useState<string | null>(null); | |
| useEffect(() => { | |
| if (!profile) return; | |
| const EditPage = () => { | |
| const location = useLocation(); | |
| const navigate = useNavigate(); | |
| - const profile = location.state.profile as GetMyProfileResponse['data']; | |
| + const profile = location.state?.profile as GetMyProfileResponse['data'] | undefined; | |
| + | |
| + useEffect(() => { | |
| + if (!profile) { | |
| + // 프로필 데이터가 없으면 마이페이지로 리다이렉트 | |
| + navigate('/mypage', { replace: true }); | |
| + } | |
| + }, [profile, navigate]); | |
| const [nickname, setNickname] = useState<string | null>(null); | |
| - const [currentNickname] = useState(profile.nickname || ''); | |
| + const [currentNickname] = useState(profile?.nickname || ''); | |
| const [nicknameError, setNicknameError] = useState(''); | |
| const maxLength = 10; | |
| const [genres, setGenres] = useState<Genre[]>([]); | |
| const [selectedId, setSelectedId] = useState<string | null>(null); | |
| useEffect(() => { | |
| if (!profile) return; | |
| // … |
🤖 Prompt for AI Agents
In src/pages/mypage/EditPage.tsx around lines 10 to 25, the component reads
location.state.profile directly which can be undefined on direct URL access or
refresh; update the code to defensively handle missing state by using optional
chaining or null-coalescing when reading profile (e.g., const profile =
location.state?.profile ?? null), guard all uses of profile (return early, show
loader, or navigate back) inside useEffect before accessing its properties, and
initialize related useState values (nickname, currentNickname, genres,
selectedId) from safe defaults so the component never accesses undefined
properties.
| fetch('/genres.json') | ||
| .then(res => res.json()) | ||
| .then((data: Genre[]) => { | ||
| setGenres(data); | ||
| // aliasName과 일치하는 장르 찾기 (초기 로딩 시에만) | ||
| if (selectedId === null) { | ||
| const matchingGenre = data.find(g => g.subTitle === profile.aliasName); | ||
| if (matchingGenre) { | ||
| setSelectedId(matchingGenre.id); | ||
| } | ||
| } | ||
| }) | ||
| .catch(console.error); | ||
| }, []); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
장르 데이터 로딩 실패 시 사용자 피드백 부재
장르 데이터 로딩이 실패해도 사용자에게 알리지 않습니다. 에러 상태를 추가하여 사용자 경험을 개선하세요.
const [genres, setGenres] = useState<Genre[]>([]);
const [selectedId, setSelectedId] = useState<string | null>(null);
+const [genreLoadError, setGenreLoadError] = useState(false);
useEffect(() => {
if (!profile) return;
fetch('/genres.json')
.then(res => res.json())
.then((data: Genre[]) => {
setGenres(data);
// aliasName과 일치하는 장르 찾기
if (selectedId === null) {
const matchingGenre = data.find(g => g.subTitle === profile.aliasName);
if (matchingGenre) {
setSelectedId(matchingGenre.id);
}
}
})
- .catch(console.error);
+ .catch(error => {
+ console.error('장르 로딩 실패:', error);
+ setGenreLoadError(true);
+ // 에러 스낵바 표시 또는 에러 UI 렌더링
+ });
}, []);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| fetch('/genres.json') | |
| .then(res => res.json()) | |
| .then((data: Genre[]) => { | |
| setGenres(data); | |
| // aliasName과 일치하는 장르 찾기 (초기 로딩 시에만) | |
| if (selectedId === null) { | |
| const matchingGenre = data.find(g => g.subTitle === profile.aliasName); | |
| if (matchingGenre) { | |
| setSelectedId(matchingGenre.id); | |
| } | |
| } | |
| }) | |
| .catch(console.error); | |
| }, []); | |
| const [genres, setGenres] = useState<Genre[]>([]); | |
| const [selectedId, setSelectedId] = useState<string | null>(null); | |
| + const [genreLoadError, setGenreLoadError] = useState(false); | |
| useEffect(() => { | |
| if (!profile) return; | |
| fetch('/genres.json') | |
| .then(res => res.json()) | |
| .then((data: Genre[]) => { | |
| setGenres(data); | |
| // aliasName과 일치하는 장르 찾기 (초기 로딩 시에만) | |
| if (selectedId === null) { | |
| const matchingGenre = data.find(g => g.subTitle === profile.aliasName); | |
| if (matchingGenre) { | |
| setSelectedId(matchingGenre.id); | |
| } | |
| } | |
| }) | |
| - .catch(console.error); | |
| + .catch(error => { | |
| + console.error('장르 로딩 실패:', error); | |
| + setGenreLoadError(true); | |
| + // 에러 스낵바 표시 또는 에러 UI 렌더링 | |
| + }); | |
| }, []); |
🤖 Prompt for AI Agents
In src/pages/mypage/EditPage.tsx around lines 26 to 39, the
fetch('/genres.json') call swallows errors and provides no user feedback; add a
local error state (e.g. const [genresError, setGenresError] = useState<string |
null>(null)) and optionally a loading state, then in the .catch handler
setGenresError with a user-friendly message (and set loading false). Also clear
the error on successful load (setGenresError(null)). Finally, update the
component render to show visible feedback when genresError is non-null (a
dismissible inline message, banner, or toast) and ensure the UI disables any
dependent controls while loading or when an error exists.
| isOpen: boolean; | ||
| userId: number; | ||
| replyId: number; | ||
| commentId: number; |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
ReplyModalProps의 replyId → commentId 변경 일관성 확인 필요
타입 변경 자체는 명확합니다. 사용처가 모두 업데이트되었는지 확인만 하면 됩니다.
레거시 필드 사용 탐색:
🏁 Script executed:
#!/bin/bash
rg -n --hidden --glob '!node_modules' '\breplyId\b|ReplyId'Length of output: 191
Reply 관련 필드명 일관성 유지 필요: replyId → commentId
아래 위치들에 여전히 replyId가 남아 있어 모두 commentId로 변경해야 합니다.
- src/types/post.ts:51
- replyId: number;
- commentId: number;
- src/data/postData.ts:101, 113
-
replyId: 1,
-
commentId: 1,```diff
-
replyId: 2,
-
commentId: 2,
위 부분들뿐만 아니라 테스트, 문서, API 스펙 등에서도 빠뜨린 곳이 없는지 한 번 더 검토 부탁드립니다.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| commentId: number; | |
| // src/types/post.ts (around line 51) | |
| commentId: number; |
| commentId: number; | |
| // src/data/postData.ts (around line 101) | |
| commentId: 1, |
| commentId: number; | |
| // src/data/postData.ts (around line 113) | |
| commentId: 2, |
🤖 Prompt for AI Agents
In src/stores/usePopupStore.ts around line 31, and additionally in
src/types/post.ts:51 and src/data/postData.ts lines 101 and 113, there are
remaining uses of the old field name replyId that must be renamed to commentId
for consistency; update the property name occurrences (interfaces, types, mock
data, object literals, function params, and any destructuring) to commentId,
adjust any references/usages and import/export names accordingly, and then
search the repo for replyId to update tests, docs, and API spec examples so
nothing is left referencing replyId.
There was a problem hiding this comment.
Actionable comments posted: 1
🔭 Outside diff range comments (1)
src/data/postData.ts (1)
101-113: SubReply mock 데이터:replyId→commentId로 통일 및isDeleted필드 검증타입 정의(
ReplyData/ReplyDataProps/SubReplyDataProps) 상 대댓글·답글의 식별자는commentId이며, 대댓글에는isDeleted가 정의되어 있지 않습니다.
따라서src/data/postData.ts의 mock 데이터를 아래와 같이 수정해주세요:
- 변경 대상:
src/data/postData.ts내 대댓글 객체- 수정 사항:
replyId→commentId- (
isDeleted는 상위 댓글에만 존재하므로 대댓글에 추가할 필요 없음)예시 diff:
src/data/postData.ts replyList: [ { parentCommentCreatorNickname: 'User31', - replyId: 1, + commentId: 1, creatorId: 1234, creatorProfileImageUrl: 'https://placehold.co/36x36', creatorNickname: 'SubUser1', alias: '북러버', postDate: '2025.01.13', aliasColor: '#FFFF24', content: '맞아요, 저도 너무 좋았어요!', likeCount: 2, isLike: false, }, { parentCommentCreatorNickname: 'User35', - replyId: 2, + commentId: 2, // … }, ],
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/data/postData.ts(3 hunks)
🔇 Additional comments (1)
src/data/postData.ts (1)
99-99: 모의 데이터의 삭제 상태 플래그 추가 적절댓글에 isDeleted: false 추가로 API/타입 정합성이 개선되었습니다. 별도 이슈 없습니다.
Also applies to: 141-141
| isLike: false, | ||
| }, | ||
| { | ||
| parentCommentCreatorNickname: 'User35', |
There was a problem hiding this comment.
parentCommentCreatorNickname 값 불일치
대댓글의 parentCommentCreatorNickname은 상위 댓글 작성자 닉네임(여기서는 'User31')과 동일해야 합니다. 현재 'User35'로 오기입되어 있어 UI에서 “@상위댓글작성자에게 답글” 등의 표기가 잘못될 수 있습니다. 아래와 같이 수정해 주세요.
- parentCommentCreatorNickname: 'User35',
+ parentCommentCreatorNickname: 'User31',🤖 Prompt for AI Agents
In src/data/postData.ts around line 115, the parentCommentCreatorNickname for
the reply is incorrectly set to 'User35' but should match the parent comment's
author 'User31'; update that value to 'User31' so the UI shows the correct
“@parentAuthor” label.
#️⃣연관된 이슈
#71 [API] feeds API 연동
#74 [API] users API 연동
#75 [API] comments API 연동
📝작업 내용
- 닉네임만 변경 가능 (6개월에 한 번 가능, 기존닉네임과 동일한 닉네임X, 중복 닉네임X, 영어 대문자X, 글자수 제한, 빈 문자열 입력 등)
- 칭호만 변경 가능
3. 댓글 조회, 삭제, 작성 / 답글 작성 등 댓글 작성 관련 로직 구현 및 API 연동 완료
💬리뷰 요구사항(선택)
특별히 없습니다!
Summary by CodeRabbit