Skip to content

feat: 마이페이지 프로필 편집 API 연동#84

Merged
heeeeyong merged 13 commits intodevelopfrom
feat/api-auth
Aug 11, 2025
Merged

feat: 마이페이지 프로필 편집 API 연동#84
heeeeyong merged 13 commits intodevelopfrom
feat/api-auth

Conversation

@heeeeyong
Copy link
Collaborator

@heeeeyong heeeeyong commented Aug 11, 2025

#️⃣연관된 이슈

#71 [API] feeds API 연동
#74 [API] users API 연동
#75 [API] comments API 연동

📝작업 내용

  1. 마이페이지 조회 및 프로필 편집 API 연동 완료. 아래의 유효성 처리 모두 고려함.

- 닉네임만 변경 가능 (6개월에 한 번 가능, 기존닉네임과 동일한 닉네임X, 중복 닉네임X, 영어 대문자X, 글자수 제한, 빈 문자열 입력 등)
- 칭호만 변경 가능
image
image
image
  1. 피드 삭제, 좋아요 API 연동 완료

3. 댓글 조회, 삭제, 작성 / 답글 작성 등 댓글 작성 관련 로직 구현 및 API 연동 완료

💬리뷰 요구사항(선택)

특별히 없습니다!

Summary by CodeRabbit

  • 신규 기능
    • 피드 게시물 좋아요/좋아요 취소 서버 연동 및 피드 삭제 기능 추가.
    • 마이페이지 프로필 조회·편집(닉네임·호칭) 및 장르 선택 지원.
  • 개선
    • 댓글·대댓글 삭제 흐름 개선 및 삭제된 댓글 자리표시와 자동 새로고침 연동.
    • 답글 멘션을 닉네임으로 통일 및 입력 처리(허용 문자 필터링) 개선.
    • 피드 상세에서 라우팅 상태로 초기 탭 지정 가능.
    • 댓글 로드/요청에 게시물 타입(postType) 적용.
  • 스타일
    • 더보기 모달 배경·레이어 동작 및 표시 방식 조정.

@vercel
Copy link

vercel bot commented Aug 11, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
thip ✅ Ready (Inspect) Visit Preview 💬 Add feedback Aug 11, 2025 7:14am

@coderabbitai
Copy link

coderabbitai bot commented Aug 11, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

댓글/대댓글 타입 및 API 파라미터 변경, 피드 좋아요·삭제 API 추가 및 UI 연동, 마이페이지 프로필 조회·수정 도입, 팝업/모달 렌더 경로·스타일 조정, 닉네임 필드명 통일, 장르 라벨 데이터 변경을 포함한 코드 및 데이터 업데이트입니다.

Changes

Cohort / File(s) Change summary
장르 데이터
public/genres.json
id "science"title"과학""과학·IT"로 변경(데이터값 변경, 구조 불변).
댓글 API 및 타입
src/api/comments/getComments.ts, src/types/post.ts
Comment/Reply 타입에 isDeleted 추가; Reply 관련 필드 재배치(예: replyId/parentCommentCreatorNickname/commentId 변경); GetCommentsParams에 `postType: 'FEED'
피드 API 추가 및 연동
src/api/feeds/deleteFeedPost.ts, src/api/feeds/postFeedLike.ts, src/components/common/Post/PostFooter.tsx
deleteFeedPost, postFeedLike 모듈 추가. PostFooter에서 좋아요 토글을 서버 호출(postFeedLike)로 전환하고 응답 기반으로 isLiked/likeCount 갱신.
댓글/대댓글 UI·삭제 흐름 변경
src/components/common/Post/Reply.tsx, src/components/common/Post/SubReply.tsx, src/components/common/Post/ReplyList.tsx, src/data/postData.ts
Reply/SubReply에 삭제 API 및 삭제 UI(isDeleted) 추가, onDelete 콜백 도입. ReplyList가 onReload 수용 및 키 생성 안정성 강화. 목 데이터에 isDeleted 및 parentCommentCreatorNickname 필드 추가. 컴포넌트 props/타입 시그니처 변경.
팝업/모달 렌더링·스타일 변경
src/components/common/Modal/MoreMenu.tsx, src/components/common/Modal/PopupContainer.tsx, src/components/common/Modal/ReplyModal.tsx, src/stores/usePopupStore.ts
MoreMenu 백드롭 스타일·z-index/정렬 변경, PopupContainer의 moremenu 경로에서 Wrapper 제거(직접 렌더), ReplyModal의 prop명 replyIdcommentId로 변경 및 popup store 동기화.
닉네임 네이밍 통일
src/components/today-words/MessageInput.tsx, src/hooks/useReplyActions.ts, src/stores/useReplyStore.ts
targetUserNamenickname으로 prop/스토어/훅 반환값 통일. startReply 인자명 변경: 첫 인자가 parentCommentCreatorNickname으로 명시적 변경.
Feed 페이지 및 상세 변경
src/pages/feed/Feed.tsx, src/pages/feed/FeedDetailPage.tsx
Feed 진입 시 location.state.initialTab 적용 및 1회성 초기화 추가. FeedDetailPage에서 getComments 호출에 postType: 'FEED' 적용, 댓글 재로딩 함수 추가, 피드 삭제에 deleteFeedPost 사용 및 삭제 후 리다이렉트/스낵바 처리, MessageInput에 nickname 전달.
마이페이지 프로필 연동
src/api/users/getMyProfile.ts, src/api/users/patchProfile.ts, src/pages/mypage/Mypage.tsx, src/pages/mypage/EditPage.tsx
내 프로필 조회(getMyProfile) 및 수정(patchProfile) API 추가. Mypage에서 실제 프로필 로딩, EditPage에서 장르 매핑/선택 및 patchProfile 호출과 서버 코드별 메시지 매핑, 입력 규칙 적용.
기타 사소 변경
src/components/common/NavBar.tsx
NavBar가 외부 FabProps 의존 제거하고 로컬 NavBarProps 선언으로 대체(타입 정의 변경).

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: 좋아요 상태 및 카운트 갱신
Loading
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 또는 리로드 트리거
Loading
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: 댓글 목록 갱신
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

✨ Feature

Suggested reviewers

  • ho0010

Poem

토끼가 코드를 콕콕, 당근은 반짝
좋아요 탁! 댓글은 사라지고 찰칵
닉네임은 통일, 프로필 반짝빛
메뉴는 살랑, 수정은 삐약삐약
배포 축하! 당근 한 조각 🥕


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e781b96 and 4fff389.

📒 Files selected for processing (2)
  • src/api/users/getMyProfile.ts (1 hunks)
  • src/pages/mypage/Mypage.tsx (2 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/api-auth

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 에러 타입 처리를 개선할 수 있습니다.

현재 수동으로 타입 단언을 하고 있는데, axiosAxiosError 타입을 사용하면 더 안전한 타입 처리가 가능합니다.

+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

📥 Commits

Reviewing files that changed from the base of the PR and between e1611cf and 04c4ccb.

📒 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 호환성에 문제가 없습니다.

Comment on lines +1 to +22
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;
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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 200

Length 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.

Comment on lines +74 to +79
interface NavBarProps {
src?: string;
path?: string;
}

const NavBar = ({ src, path }: NavBarProps) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Comment on lines +74 to +88
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);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

좋아요 토글 동시 클릭(중복 요청) 시 레이스/역순 응답으로 카운트가 틀어질 수 있습니다

  • 여러 번 빠르게 클릭 시 요청이 겹치고, 응답이 역순으로 도착하면 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.

Comment on lines +59 to 90
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);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Comment on lines +72 to 90
setTimeout(() => {
openSnackbar({
message: '댓글 삭제에 실패했습니다.',
variant: 'top',
onClose: closePopup,
});
}, 100);
}
} catch (error) {
console.error('댓글 삭제 실패:', error);
closePopup();
setTimeout(() => {
openSnackbar({
message: '댓글 삭제에 실패했습니다.',
variant: 'top',
onClose: closePopup,
});
}, 100);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Suggested change
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.

Comment on lines 137 to 147
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 <></>;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

로딩 및 에러 상태 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.

Suggested change
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.

Comment on lines 10 to +25
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;

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
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.

Comment on lines +26 to +39
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);
}, []);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Suggested change
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;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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 관련 필드명 일관성 유지 필요: replyIdcommentId

아래 위치들에 여전히 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.

Suggested change
commentId: number;
// src/types/post.ts (around line 51)
commentId: number;
Suggested change
commentId: number;
// src/data/postData.ts (around line 101)
commentId: 1,
Suggested change
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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🔭 Outside diff range comments (1)
src/data/postData.ts (1)

101-113: SubReply mock 데이터: replyIdcommentId로 통일 및 isDeleted 필드 검증

타입 정의(ReplyData/ReplyDataProps/SubReplyDataProps) 상 대댓글·답글의 식별자는 commentId이며, 대댓글에는 isDeleted가 정의되어 있지 않습니다.
따라서 src/data/postData.ts의 mock 데이터를 아래와 같이 수정해주세요:

  • 변경 대상: src/data/postData.ts 내 대댓글 객체
  • 수정 사항:
    • replyIdcommentId
    • (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

📥 Commits

Reviewing files that changed from the base of the PR and between 04c4ccb and e781b96.

📒 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',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

@heeeeyong heeeeyong merged commit 546feff into develop Aug 11, 2025
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant