Skip to content

feat: comments api 구현#80

Merged
heeeeyong merged 12 commits into
developfrom
feat/api-comments
Aug 7, 2025
Merged

feat: comments api 구현#80
heeeeyong merged 12 commits into
developfrom
feat/api-comments

Conversation

@heeeeyong
Copy link
Copy Markdown
Collaborator

@heeeeyong heeeeyong commented Aug 7, 2025

#️⃣연관된 이슈

#75 comments api 연동

📝작업 내용

  1. 댓글조회 API 로직 구현
    댓글 조회에서 부모 댓글이 무엇인지, 답글을 단 대상이 어떤닉네임인지만 잘 명시하도록 했습니다. 해당 부분 API 완성되는대로 마무리해서 수정하겠습니다.
  2. 댓글&답글 작성 API 연동
    postType만 잘 맞춰서 모임방 투표 혹은 모임방 기록에서 useReplyActions hook 호출하셔서 사용하면됩니다. (@ljh130334)
    (1) 일반 댓글 작성 로직
    MessageInput에서 직접 입력 -> submitComment 호출 -> postReply API 호출 -> isReplyRequest: false, parentId: null 상태 저장
    -> 서버에 댓글 저장 -> 입력창 초기화
    (2) 답글 작성 로직
    Reply.tsx에서 "답글작성" 클릭 -> startReply(nickName, commentId) 호출 -> useReplyStore에서 전역 상태 업데이트
    -> MessageInput에서 답글 UI 표시 -> submitComment 호출 -> postReply API 호출 -> isReplyRequest: true, parentId: commentId
    -> 서버에 답글 저장 -> 입력창 초기화, 답글 상태 해제
  3. 댓글 삭제 API 연동
image 답글이 있는 댓글 삭제 시, "삭제된 댓글이에요" 문구 노출 4. 댓글 좋아요 상태변경 API 연동 5. 유저검색 API 연동 Intersection Observer를 활용해 스크롤이 하단에 도달하면 loadMore을 호출해 무한스크롤을 구현하도록 했습니다. 서버에서 페이지네이션 처리를 따로 하지 않은것같아서 setHasMore(newUserList.length === size); 를 비교해 다음 데이터를 호출을 하도록 구현했는데 정확이 size와 마지막 데이터의 갯수가 맞아떨어지면 그 다음에 데이터가 추가로 있을지 없을지 판단을 하지 못하는 상황이 있습니다.

스크린샷

댓글 조회 API 연동 완료되는대로 업로드하겠습니다.

💬리뷰 요구사항

특별히 없습니다.

Summary by CodeRabbit

  • 신규 기능

    • 댓글 및 답글 삭제, 좋아요 기능을 위한 API 연동이 추가되었습니다.
    • 사용자 검색이 실시간 API 기반으로 변경되고, 무한 스크롤(인피니트 스크롤) 지원이 추가되었습니다.
    • 답글에 "더보기" 팝업 모달이 도입되어 삭제 등 부가 기능을 제공합니다.
    • 입력 지연(디바운스) 및 팝업 액션 관련 커스텀 훅이 추가되었습니다.
  • 버그 수정 및 개선

    • 댓글, 답글, 사용자 관련 데이터 구조 및 명칭이 일관성 있게 변경되었습니다.
    • 피드 상세 페이지에서 댓글 목록이 동적 API 데이터로 교체되었습니다.
    • 답글 및 서브답글의 좋아요 상태가 서버와 동기화되도록 개선되었습니다.
  • 스타일 및 UI 개선

    • 사용자 프로필 및 리스트 UI가 개선되고, 빈 상태 및 로딩 메시지가 추가되었습니다.
    • 팝업, 모달 등 UI 컴포넌트의 위치 지정 및 스타일이 개선되었습니다.
  • 기타

    • 파비콘 경로가 절대경로로 수정되었습니다.
    • 불필요한 목(mock) 데이터 사용이 제거되었습니다.
    • 네비게이션 바의 경로가 게시글 작성 페이지로 변경되었습니다.

@vercel
Copy link
Copy Markdown

vercel Bot commented Aug 7, 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 7, 2025 6:42pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 7, 2025

Walkthrough

댓글 및 사용자 검색 기능이 서버 API와 연동되도록 주요 모듈이 추가 및 수정되었습니다. 댓글 조회, 삭제, 좋아요 API가 구현되어 UI 컴포넌트와 연결되었고, 답글 관련 모달 팝업 시스템이 확장되었습니다. 사용자 검색은 실시간 API 데이터 기반으로 동작하며, 관련 타입 및 목데이터 구조가 일관성 있게 리팩토링되었습니다.

Changes

Cohort / File(s) Change Summary
댓글 API 모듈 추가
src/api/comments/deleteComment.ts, src/api/comments/getComments.ts, src/api/comments/postLike.ts
댓글 삭제, 조회, 좋아요 토글 API 모듈 신규 추가. 타입 정의와 비동기 함수 구현.
사용자 API 및 검색 훅 추가
src/api/users/getUsers.ts, src/hooks/useUserSearch.ts, src/hooks/useDebounce.ts
사용자 목록 조회 API, 사용자 검색용 커스텀 훅, 디바운스 훅 신규 추가.
팝업 시스템 확장
src/components/common/Modal/PopupContainer.tsx, src/components/common/Modal/ReplyModal.tsx, src/hooks/usePopupActions.ts, src/stores/usePopupStore.ts
답글 전용 reply-modal 팝업 타입/컴포넌트/스토어/액션 추가.
댓글/답글 UI 및 데이터 구조 리팩토링
src/components/common/Post/Reply.tsx, src/components/common/Post/ReplyList.tsx, src/components/common/Post/SubReply.tsx, src/data/postData.ts, src/types/post.ts
댓글/답글 컴포넌트 및 목데이터, 타입 구조를 API 응답과 일치하도록 리팩토링 및 prop 이름 정비, 좋아요 동작 서버 연동, reply-modal 연동.
피드 상세 및 사용자 검색 페이지 개선
src/pages/feed/FeedDetailPage.tsx, src/pages/feed/UserSearch.tsx, src/pages/feed/UserSearchResult.tsx
댓글 목록, 사용자 검색 결과를 API 기반으로 변경, 무한스크롤 및 로딩 처리 추가.
프로필 및 사용자 타입/컴포넌트 정비
src/components/feed/UserProfileItem.tsx, src/types/user.ts, src/types/profile.ts
프로필 관련 prop 및 타입명 정비(nickName → nickname 등), 불필요한 필드 삭제.
파비콘 경로 수정
index.html
파비콘 경로를 상대 → 절대 경로(/assets/custom_favicon.svg)로 변경.
네비게이션 경로 변경
src/pages/feed/Feed.tsx, src/pages/feed/OtherFeedPage.tsx
NavBar 컴포넌트의 path prop을 루트(/)에서 /post/create로 변경.

Sequence Diagram(s)

sequenceDiagram
  participant UI as Reply/SubReply 컴포넌트
  participant Popup as PopupContainer/ReplyModal
  participant API as 댓글 API 모듈

  UI->>API: getComments(postId)
  API-->>UI: 댓글/답글 리스트 반환

  UI->>Popup: openReplyModal({replyId, ...})
  Popup->>UI: 삭제/신고 선택 UI 표시

  Popup->>API: deleteComment(replyId)
  API-->>Popup: 삭제 결과 반환
  Popup->>UI: 성공 시 댓글/답글 목록 갱신

  UI->>API: postLike(replyId, type)
  API-->>UI: 좋아요 상태/카운트 반환
  UI->>UI: 상태 반영
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20분

Possibly related issues

Possibly related PRs

Suggested labels

✨ Feature

Poem

🐇
댓글과 답글, 토글과 팝업,
서버와 대화하며 토끼는 깡총!
사용자 검색은 실시간,
좋아요, 삭제도 API와 찰떡.
팝업이 열리고,
데이터가 흐르면,
오늘도 코드밭엔 초록이 무성!

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.


📜 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 9ff6322 and 7469da9.

📒 Files selected for processing (1)
  • src/data/userData.ts (4 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/data/userData.ts
✨ 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-comments

🪧 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
Copy Markdown

@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: 7

🧹 Nitpick comments (6)
src/components/common/Modal/ReplyModal.tsx (1)

62-64: 주석 처리된 신고 기능

신고 기능이 주석 처리되어 있습니다. 향후 구현 예정인지 확인이 필요합니다.

신고 기능 구현을 도와드릴까요? 새로운 이슈로 등록하시겠습니까?

src/api/comments/deleteComment.ts (1)

3-5: 불필요한 인터페이스 정의

DeleteCommentRequest 인터페이스가 정의되어 있지만 실제로는 사용되지 않습니다. 경로 매개변수를 사용하므로 별도 요청 인터페이스가 필요하지 않습니다.

다음과 같이 인터페이스를 제거하세요:

-export interface DeleteCommentRequest {
-  commentId: number;
-}
-
src/api/comments/getComments.ts (1)

57-58: URL 구성 로직을 단순화하세요.

URLSearchParams는 빈 파라미터를 자동으로 처리하므로 조건부 URL 구성이 불필요합니다.

다음과 같이 단순화할 수 있습니다:

-  const queryString = searchParams.toString();
-  const url = queryString ? `/comments/${postId}?${queryString}` : `/comments/${postId}`;
+  const queryString = searchParams.toString();
+  const url = `/comments/${postId}${queryString ? `?${queryString}` : ''}`;
src/pages/feed/UserSearchResult.tsx (2)

22-25: 불필요하게 복잡한 빈 배열 체크 로직을 단순화하세요.

배열이 비어있는지 확인하는 로직이 지나치게 장황합니다.

다음과 같이 간단하게 개선할 수 있습니다:

-  const isEmptySearchedUserList = () => {
-    if (searchedUserList.length === 0) return true;
-    else return false;
-  };
+  const isEmptySearchedUserList = () => searchedUserList.length === 0;

또는 함수 없이 직접 사용:

-  {isEmptySearchedUserList() ? (
+  {searchedUserList.length === 0 ? (

30-45: Intersection Observer 정리 로직을 개선하세요.

Observer 정리 시 observerRef.current 체크가 누락되어 있습니다.

다음과 같이 개선하세요:

-    return () => observer.disconnect();
+    return () => {
+      if (observerRef.current) {
+        observer.unobserve(observerRef.current);
+      }
+      observer.disconnect();
+    };
src/components/common/Post/SubReply.tsx (1)

73-73: 프로필 이미지 URL 처리를 개선하세요.

빈 문자열 대신 undefined를 사용하는 것이 더 적절합니다.

-          creatorProfileImageUrl={creatorProfileImageUrl || ''}
+          creatorProfileImageUrl={creatorProfileImageUrl || undefined}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between c7242cc and 61a4b75.

⛔ Files ignored due to path filters (1)
  • src/assets/header/header-logo.svg is excluded by !**/*.svg
📒 Files selected for processing (22)
  • index.html (1 hunks)
  • src/api/comments/deleteComment.ts (1 hunks)
  • src/api/comments/getComments.ts (1 hunks)
  • src/api/comments/postLike.ts (1 hunks)
  • src/api/users/getUsers.ts (1 hunks)
  • src/components/common/Modal/PopupContainer.tsx (2 hunks)
  • src/components/common/Modal/ReplyModal.tsx (1 hunks)
  • src/components/common/Post/Reply.tsx (2 hunks)
  • src/components/common/Post/ReplyList.tsx (1 hunks)
  • src/components/common/Post/SubReply.tsx (3 hunks)
  • src/components/feed/UserProfileItem.tsx (4 hunks)
  • src/data/postData.ts (1 hunks)
  • src/hooks/useDebounce.ts (1 hunks)
  • src/hooks/usePopupActions.ts (2 hunks)
  • src/hooks/useUserSearch.ts (1 hunks)
  • src/pages/feed/FeedDetailPage.tsx (3 hunks)
  • src/pages/feed/UserSearch.tsx (2 hunks)
  • src/pages/feed/UserSearchResult.tsx (3 hunks)
  • src/stores/usePopupStore.ts (2 hunks)
  • src/types/post.ts (1 hunks)
  • src/types/profile.ts (0 hunks)
  • src/types/user.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • src/types/profile.ts
🧰 Additional context used
🧬 Code Graph Analysis (9)
src/pages/feed/UserSearch.tsx (2)
src/hooks/useUserSearch.ts (1)
  • useUserSearch (11-80)
src/pages/feed/UserSearchResult.tsx (1)
  • UserSearchResult (15-70)
src/hooks/usePopupActions.ts (1)
src/stores/usePopupStore.ts (1)
  • ReplyModalProps (28-37)
src/components/common/Modal/PopupContainer.tsx (1)
src/stores/usePopupStore.ts (1)
  • ReplyModalProps (28-37)
src/pages/feed/UserSearchResult.tsx (1)
src/api/users/getUsers.ts (1)
  • UserData (3-10)
src/api/users/getUsers.ts (1)
src/api/index.ts (1)
  • apiClient (27-33)
src/api/comments/getComments.ts (1)
src/api/index.ts (1)
  • apiClient (27-33)
src/hooks/useUserSearch.ts (2)
src/api/users/getUsers.ts (2)
  • UserData (3-10)
  • getUsers (26-42)
src/hooks/useDebounce.ts (1)
  • useDebounce (3-17)
src/api/comments/deleteComment.ts (1)
src/api/index.ts (1)
  • apiClient (27-33)
src/api/comments/postLike.ts (1)
src/api/index.ts (1)
  • apiClient (27-33)
🔇 Additional comments (33)
index.html (1)

5-5: 절대 경로로 지정된 favicon 경로 호환성 검증 필요

  • 위치: index.html 5행
  • 현재 코드:
    <link rel="icon" type="image/svg+xml" href="/assets/custom_favicon.svg" />
  • 확인 결과: public/assets/custom_favicon.svg 파일이 리포지토리에 존재합니다.

그러나 /assets/...와 같은 루트 절대 경로를 사용할 경우
– 애플리케이션을 /app/ 등의 서브 디렉터리에 배포하거나
– Vite/Vercel 등에서 base 옵션을 설정했을 때

favicon이 로드되지 않을 수 있습니다.

해결 방안 중 하나를 선택해 적용하거나, 배포 환경에서 실제 로드 여부를 확인해주세요:

  • 상대 경로 사용
    <link rel="icon" href="./assets/custom_favicon.svg" />
  • Vite의 import.meta.env.BASE_URL 활용
    <link rel="icon" href="${import.meta.env.BASE_URL}assets/custom_favicon.svg" />
  • Vite 설정(vite.config.js)의 base 옵션을 적절히 지정하고, 빌드 후 경로 매핑을 검증
src/types/user.ts (1)

5-5: 명명 규칙 통일이 잘 적용되었습니다.

nickName에서 nickname으로의 변경은 일관된 명명 규칙을 위한 좋은 개선입니다. 다른 파일들에서도 동일하게 적용된 것을 확인했습니다.

src/hooks/useDebounce.ts (1)

3-17: 디바운스 훅 구현이 우수합니다.

표준적인 디바운스 패턴을 잘 구현했습니다. 제네릭 타입 사용으로 재사용성이 높고, useEffect의 의존성 배열과 클린업 함수가 올바르게 설정되어 메모리 누수를 방지합니다.

src/hooks/usePopupActions.ts (3)

5-5: 타입 임포트가 적절합니다.

ReplyModalProps 타입이 올바르게 임포트되었습니다.


23-26: 댓글 모달 액션 함수가 일관된 패턴으로 구현되었습니다.

기존 팝업 액션들과 동일한 패턴을 따라 구현되어 코드 일관성이 유지되었습니다.


32-32: 반환 객체에 새로운 함수가 올바르게 추가되었습니다.

openReplyModal이 적절히 노출되어 다른 컴포넌트에서 사용할 수 있습니다.

src/components/common/Modal/PopupContainer.tsx (3)

6-6: ReplyModal 컴포넌트 임포트가 적절합니다.


8-13: 타입 임포트가 올바르게 추가되었습니다.

ReplyModalProps 타입이 적절히 임포트되어 타입 안전성이 보장됩니다.


47-49: ReplyModal 자체 위치 제어 방식 검증 완료

ReplyModal 컴포넌트 내부에서 아래와 같이 OverlayModalContainerposition: fixed를 적용하고, 전달된 position prop을 기반으로 left/top 값을 계산하고 있습니다.
따라서 별도의 외부 Wrapper 없이 렌더링하는 것은 의도된 설계이며, 추가 수정은 필요 없습니다.

src/components/feed/UserProfileItem.tsx (4)

11-11: prop 명명 변경이 일관되게 적용되었습니다.

타입 정의와 일치하게 nickname으로 변경되었습니다.


34-34: 콘솔 로깅에서도 업데이트된 prop명이 올바르게 사용되었습니다.


46-46: JSX에서 nickname prop이 올바르게 사용되었습니다.


70-72: 반응형 제약 조건이 적절히 추가되었습니다.

max-width, min-width, margin: 0 auto 스타일 추가로 다양한 화면 크기에서의 일관된 레이아웃이 보장됩니다.

src/components/common/Post/ReplyList.tsx (1)

16-17: 속성명 변경이 올바르게 적용되었습니다.

replyCommentListreplyList, replyCommentIdreplyId로 변경된 것이 타입 정의와 일관성 있게 적용되어 코드의 가독성이 향상되었습니다.

src/pages/feed/UserSearch.tsx (2)

11-11: useUserSearch 훅 통합이 잘 구현되었습니다.

정적 데이터를 동적 검색으로 교체하여 실제 API와 연동된 사용자 검색 기능을 제공합니다. 디바운스 지연 300ms와 페이지 크기 20은 적절한 설정입니다.

Also applies to: 19-23


92-96: 무한 스크롤을 위한 props 전달이 적절합니다.

UserSearchResult 컴포넌트에 loading, hasMore, onLoadMore props를 전달하여 무한 스크롤 기능이 올바르게 구현되었습니다. 검색 중(searching)과 검색 완료(searched) 상태 모두에서 동일한 데이터를 사용하는 것이 일관성 있습니다.

Also applies to: 100-104

src/pages/feed/FeedDetailPage.tsx (4)

13-13: 댓글 API 통합이 적절하게 구현되었습니다.

getComments API를 import하고 commentList 상태를 추가하여 동적 댓글 로딩을 지원합니다. 타입 안전성도 CommentData[]로 보장되어 있습니다.

Also applies to: 19-19


27-32: 페이지 언마운트 시 답글 상태 정리가 잘 구현되었습니다.

컴포넌트가 언마운트될 때 cancelReply()를 호출하여 답글 작성 상태를 초기화하는 것은 메모리 누수 방지와 사용자 경험 개선에 도움이 됩니다.


34-64: Promise.all을 사용한 병렬 데이터 로딩이 효율적입니다.

피드 상세 정보와 댓글 목록을 병렬로 로드하여 성능을 최적화했습니다. 에러 처리도 두 API 호출을 함께 관리하여 일관된 사용자 경험을 제공합니다.


128-128: 동적 댓글 리스트 연동이 완료되었습니다.

정적 mock 데이터 대신 API로부터 로드된 commentList를 사용하여 실시간 댓글 데이터를 표시합니다.

src/data/postData.ts (2)

89-92: 댓글 데이터 구조의 네이밍 통일이 잘 되었습니다.

userIdcreatorId, imageUrlcreatorProfileImageUrl 등 일관된 네이밍 컨벤션으로 변경하여 API 응답 구조와 일치시켰습니다. 코드 가독성과 유지보수성이 향상되었습니다.


99-99: 답글 데이터 구조도 일관되게 리팩토링되었습니다.

replyCommentListreplyList, replyCommentIdreplyId 등 답글 관련 속성들이 메인 댓글과 동일한 패턴으로 변경되어 데이터 구조의 일관성이 확보되었습니다.

Also applies to: 101-108, 113-120

src/stores/usePopupStore.ts (3)

4-4: PopupType에 'reply-modal' 추가가 적절합니다.

기존 팝업 시스템을 확장하여 답글 모달 지원을 추가했습니다. 타입 안전성을 유지하면서 깔끔하게 확장되었습니다.


28-37: ReplyModalProps 인터페이스 설계가 잘 되었습니다.

userId, replyId 등 필수 정보와 선택적 position 속성을 포함하여 답글 모달의 다양한 사용 사례를 지원합니다. 특히 position 속성으로 클릭한 위치에 맞춰 모달을 배치할 수 있어 UX가 향상됩니다.


42-42: 기존 팝업 시스템과의 통합이 일관성 있게 구현되었습니다.

PopupState 인터페이스와 openPopup 메서드가 새로운 ReplyModalProps를 포함하도록 확장되었으며, 기존 코드에 영향을 주지 않으면서 타입 안전성을 보장합니다.

Also applies to: 44-47

src/api/users/getUsers.ts (1)

3-10: 사용자 데이터 인터페이스가 잘 정의됨

사용자 데이터 구조가 명확하고 일관성 있게 정의되어 있습니다.

src/components/common/Modal/ReplyModal.tsx (1)

12-24: localStorage 접근이 안전하게 처리됨

try-catch 블록으로 localStorage 접근 시 발생할 수 있는 에러를 적절히 처리하고 있습니다.

src/hooks/useUserSearch.ts (4)

11-16: 상태 관리가 적절히 구성됨

사용자 검색에 필요한 모든 상태가 적절히 정의되어 있습니다. 디폴트 값 설정도 합리적입니다.


19-53: 검색 함수 로직이 잘 구현됨

디바운스된 키워드로 검색하고, 로딩/에러 상태를 적절히 관리하며, 무한 스크롤을 위한 결과 병합 로직이 잘 구현되어 있습니다.


44-44: 무한 스크롤 판단 로직의 한계점 인지 필요

PR 목표에서 언급된 것처럼, 반환된 데이터 길이가 요청 크기와 정확히 일치할 때 더 많은 데이터가 있는지 판단하기 어려운 문제가 있습니다. 현재 구현은 서버에서 페이지네이션을 명시적으로 지원하지 않는 상황에서의 최선의 접근 방식입니다.

향후 서버 API가 총 데이터 수나 hasMore 플래그를 제공하도록 개선될 예정인지 확인해보세요.


60-64: loadMore 함수가 안전하게 구현됨

로딩 중이거나 더 이상 데이터가 없을 때, 또는 검색어가 없을 때 중복 요청을 방지하는 로직이 잘 구현되어 있습니다.

src/components/common/Post/Reply.tsx (1)

37-38: 좋아요 수 업데이트 로직의 정확성을 검증하세요.

현재 로직은 단순히 +1/-1을 하고 있는데, 이는 동시성 문제나 네트워크 지연 시 실제 서버 상태와 불일치할 수 있습니다.

서버 응답에서 정확한 좋아요 수를 반환하는지 확인하고, 가능하다면 서버에서 반환한 값을 직접 사용하는 것이 좋습니다:

-      setLikeCount(prev => (response.data.isLiked ? prev + 1 : prev - 1));
+      // 서버에서 정확한 likeCount를 반환한다면:
+      // setLikeCount(response.data.likeCount);
src/types/post.ts (1)

42-68: 타입 정의가 일관성 있게 잘 개선되었습니다!

creator 접두사를 사용한 일관된 네이밍 컨벤션과 nullable 타입 처리가 적절합니다.

Comment on lines +16 to +21
export const deleteComment = async (commentId: number) => {
const response = await apiClient.delete<DeleteCommentResponse>(`/comments/${commentId}`, {
data: { commentId },
});
return response.data;
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

HTTP DELETE 요청 방식 개선 및 반환 타입 명시 필요

  1. DELETE 요청에서 body에 데이터를 보내는 것은 HTTP 표준에 맞지 않습니다. 경로 매개변수에 이미 commentId가 있으므로 body는 불필요합니다.
  2. 함수 반환 타입이 명시되지 않았습니다.

다음과 같이 수정하세요:

-export const deleteComment = async (commentId: number) => {
-  const response = await apiClient.delete<DeleteCommentResponse>(`/comments/${commentId}`, {
-    data: { commentId },
-  });
+export const deleteComment = async (commentId: number): Promise<DeleteCommentResponse> => {
+  const response = await apiClient.delete<DeleteCommentResponse>(`/comments/${commentId}`);
   return response.data;
 };
🤖 Prompt for AI Agents
In src/api/comments/deleteComment.ts lines 16 to 21, the deleteComment function
sends commentId in the request body of a DELETE request, which is against HTTP
standards since the ID is already in the URL path. Remove the data property from
the request options. Also, explicitly declare the function's return type to
match the expected response data type for clarity and type safety.

Comment on lines +46 to +62
export const getComments = async (postId: number, params?: GetCommentsParams) => {
const searchParams = new URLSearchParams();

if (params?.size) {
searchParams.append('size', params.size.toString());
}

if (params?.cursor) {
searchParams.append('cursor', params.cursor);
}

const queryString = searchParams.toString();
const url = queryString ? `/comments/${postId}?${queryString}` : `/comments/${postId}`;

const response = await apiClient.get<GetCommentsResponse>(url);
return response.data;
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

postId 유효성 검증 및 에러 처리를 추가하세요.

postId 파라미터 검증과 API 호출 실패에 대한 에러 처리가 누락되어 있습니다.

다음과 같이 개선하세요:

 export const getComments = async (postId: number, params?: GetCommentsParams) => {
+  if (!postId || postId <= 0) {
+    throw new Error('유효하지 않은 postId입니다');
+  }
+
   const searchParams = new URLSearchParams();

   if (params?.size) {
     searchParams.append('size', params.size.toString());
   }

   if (params?.cursor) {
     searchParams.append('cursor', params.cursor);
   }

   const queryString = searchParams.toString();
   const url = queryString ? `/comments/${postId}?${queryString}` : `/comments/${postId}`;

-  const response = await apiClient.get<GetCommentsResponse>(url);
-  return response.data;
+  try {
+    const response = await apiClient.get<GetCommentsResponse>(url);
+    return response.data;
+  } catch (error) {
+    console.error('댓글 조회 실패:', error);
+    throw error;
+  }
 };
📝 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
export const getComments = async (postId: number, params?: GetCommentsParams) => {
const searchParams = new URLSearchParams();
if (params?.size) {
searchParams.append('size', params.size.toString());
}
if (params?.cursor) {
searchParams.append('cursor', params.cursor);
}
const queryString = searchParams.toString();
const url = queryString ? `/comments/${postId}?${queryString}` : `/comments/${postId}`;
const response = await apiClient.get<GetCommentsResponse>(url);
return response.data;
};
export const getComments = async (postId: number, params?: GetCommentsParams) => {
if (!postId || postId <= 0) {
throw new Error('유효하지 않은 postId입니다');
}
const searchParams = new URLSearchParams();
if (params?.size) {
searchParams.append('size', params.size.toString());
}
if (params?.cursor) {
searchParams.append('cursor', params.cursor);
}
const queryString = searchParams.toString();
const url = queryString
? `/comments/${postId}?${queryString}`
: `/comments/${postId}`;
try {
const response = await apiClient.get<GetCommentsResponse>(url);
return response.data;
} catch (error) {
console.error('댓글 조회 실패:', error);
throw error;
}
};
🤖 Prompt for AI Agents
In src/api/comments/getComments.ts around lines 46 to 62, the function lacks
validation for the postId parameter and does not handle errors from the API
call. Add a check at the start of the function to ensure postId is a valid
positive number and throw an error if not. Wrap the API call in a try-catch
block to catch any request failures and handle or rethrow the error
appropriately to improve robustness.

Comment on lines +17 to +22
export const postLike = async (commentId: number, type: boolean) => {
const response = await apiClient.post<PostLikeResponse>(`/comments/${commentId}/likes`, {
type,
});
return response.data;
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

함수 반환 타입 명시와 에러 처리 개선 필요

현재 함수가 response.data를 반환하지만 명시적인 반환 타입이 없고, API 호출 실패 시 에러 처리가 없습니다.

다음 개선사항을 적용하세요:

-export const postLike = async (commentId: number, type: boolean) => {
+export const postLike = async (commentId: number, type: boolean): Promise<PostLikeResponse["data"]> => {
   const response = await apiClient.post<PostLikeResponse>(`/comments/${commentId}/likes`, {
     type,
   });
   return response.data;
 };
📝 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
export const postLike = async (commentId: number, type: boolean) => {
const response = await apiClient.post<PostLikeResponse>(`/comments/${commentId}/likes`, {
type,
});
return response.data;
};
export const postLike = async (commentId: number, type: boolean): Promise<PostLikeResponse["data"]> => {
const response = await apiClient.post<PostLikeResponse>(`/comments/${commentId}/likes`, {
type,
});
return response.data;
};
🤖 Prompt for AI Agents
In src/api/comments/postLike.ts around lines 17 to 22, the postLike function
lacks an explicit return type and does not handle errors from the API call. To
fix this, explicitly declare the function's return type as a Promise of
PostLikeResponse and wrap the API call in a try-catch block to catch and handle
any errors, such as logging or rethrowing them appropriately.

Comment thread src/api/users/getUsers.ts
Comment on lines +26 to +42
export const getUsers = async (params?: GetUsersParams) => {
const searchParams = new URLSearchParams();

if (params?.keyword) {
searchParams.append('keyword', params.keyword);
}

if (params?.size) {
searchParams.append('size', params.size.toString());
}

const queryString = searchParams.toString();
const url = queryString ? `/users?${queryString}` : '/users';

const response = await apiClient.get<GetUsersResponse>(url);
return response.data;
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

함수 반환 타입 명시 필요

함수의 반환 타입이 명시되지 않았습니다. TypeScript 모범 사례에 따라 명시적인 반환 타입을 추가하세요.

-export const getUsers = async (params?: GetUsersParams) => {
+export const getUsers = async (params?: GetUsersParams): Promise<GetUsersResponse> => {
🤖 Prompt for AI Agents
In src/api/users/getUsers.ts between lines 26 and 42, the getUsers function
lacks an explicit return type. Add a TypeScript return type annotation to the
function signature that matches the expected resolved type of the promise, such
as Promise<GetUsersResponse>, to clearly specify what the function returns.

Comment on lines +79 to +87
{isMyReply ? (
<OptionItem className="report">
<div className="option-text">신고하기</div>
</OptionItem>
) : (
<OptionItem onClick={handleDelete} className="delete">
<div className="option-text">삭제하기</div>
</OptionItem>
)}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

조건문 로직 오류 - 즉시 수정 필요

조건문의 로직이 반대로 구현되어 있습니다. 내 댓글인 경우 "삭제하기"가 표시되어야 하는데 "신고하기"가 표시되고, 다른 사람 댓글인 경우 "신고하기"가 표시되어야 하는데 "삭제하기"가 표시됩니다.

다음과 같이 수정하세요:

-          {isMyReply ? (
-            <OptionItem className="report">
-              <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>
           )}
📝 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
{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>
)}
🤖 Prompt for AI Agents
In src/components/common/Modal/ReplyModal.tsx between lines 79 and 87, the
conditional rendering logic is reversed: when isMyReply is true, it incorrectly
shows "신고하기" instead of "삭제하기", and vice versa. Swap the JSX blocks inside the
ternary operator so that when isMyReply is true, the "삭제하기" OptionItem with
onClick={handleDelete} is rendered, and when false, the "신고하기" OptionItem
without onClick is rendered.

Comment on lines +34 to +35
console.log('좋아요 상태 변경 성공:', response);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

프로덕션 코드에서 console.log를 제거하세요.

디버깅용 console.log가 프로덕션 코드에 남아있습니다.

다음과 같이 제거하거나 적절한 로깅 시스템으로 대체하세요:

   const handleLike = async () => {
     try {
       const response = await postLike(commentId, !liked);
-      console.log('좋아요 상태 변경 성공:', response);

       // 서버 응답으로 상태 업데이트
       setLiked(response.data.isLiked);
       setLikeCount(prev => (response.data.isLiked ? prev + 1 : prev - 1));
     } catch (error) {
-      console.error('좋아요 상태 변경 실패:', error);
+      // 적절한 에러 처리 (예: 토스트 메시지 표시)
     }
   };

Also applies to: 40-41

🤖 Prompt for AI Agents
In src/components/common/Post/Reply.tsx at lines 34-35 and 40-41, remove the
console.log statements used for debugging the like status change success. These
logs should be deleted or replaced with a proper logging system if necessary to
avoid cluttering production code with console outputs.

Comment on lines +40 to +41
console.log('좋아요 상태 변경 성공:', response);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

프로덕션 코드에서 console.log를 제거하세요.

디버깅용 console.log가 프로덕션 코드에 남아있습니다.

Reply.tsx와 동일한 문제입니다. 다음과 같이 제거하세요:

   const handleLike = async () => {
     try {
       const response = await postLike(replyId, !liked);
-      console.log('좋아요 상태 변경 성공:', response);

       // 서버 응답으로 상태 업데이트
       setLiked(response.data.isLiked);
       setCurrentLikeCount(prev => (response.data.isLiked ? prev + 1 : prev - 1));
     } catch (error) {
-      console.error('좋아요 상태 변경 실패:', error);
+      // 적절한 에러 처리 (예: 토스트 메시지 표시)
     }
   };

Also applies to: 46-47

🤖 Prompt for AI Agents
In src/components/common/Post/SubReply.tsx at lines 40-41 and 46-47, remove the
console.log statements used for debugging the like status change success. These
console.log calls should not be present in production code. Simply delete these
lines to clean up the code.

Copy link
Copy Markdown

@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: 6

🧹 Nitpick comments (6)
src/api/comments/postLike.ts (1)

17-22: 에러 핸들링이 필요합니다.

API 호출 실패 시 적절한 에러 처리를 추가하는 것을 고려해보세요.

export const postLike = async (commentId: number, type: boolean) => {
-  const response = await apiClient.post<PostLikeResponse>(`/comments/${commentId}/likes`, {
-    type,
-  });
-  return response.data;
+  try {
+    const response = await apiClient.post<PostLikeResponse>(`/comments/${commentId}/likes`, {
+      type,
+    });
+    return response.data;
+  } catch (error) {
+    console.error('좋아요 처리 실패:', error);
+    throw error;
+  }
};
src/api/comments/deleteComment.ts (1)

16-21: API 호출 방식 검토 필요

URL 패스와 요청 본문에 모두 commentId를 포함하고 있습니다. 일반적으로 DELETE 요청에서는 URL 패스의 ID만으로 충분하므로 요청 본문의 commentId가 필요한지 확인이 필요합니다.

요청 본문이 불필요하다면 다음과 같이 단순화할 수 있습니다:

 export const deleteComment = async (commentId: number) => {
-  const response = await apiClient.delete<DeleteCommentResponse>(`/comments/${commentId}`, {
-    data: { commentId },
-  });
+  const response = await apiClient.delete<DeleteCommentResponse>(`/comments/${commentId}`);
   return response.data;
 };
src/pages/feed/UserSearchResult.tsx (2)

22-25: 불필요하게 복잡한 함수 단순화

isEmptySearchedUserList 함수를 더 간단하게 작성할 수 있습니다.

  const isEmptySearchedUserList = () => {
-    if (searchedUserList.length === 0) return true;
-    else return false;
+    return searchedUserList.length === 0;
  };

30-45: Intersection Observer 의존성 최적화 필요

onLoadMore 함수가 부모 컴포넌트에서 매번 재생성될 경우, 불필요한 Observer 재연결이 발생할 수 있습니다. 부모 컴포넌트에서 useCallback으로 메모이제이션하거나, 의존성 배열을 최적화해야 합니다.

부모 컴포넌트에서 onLoadMore를 다음과 같이 메모이제이션하세요:

const onLoadMore = useCallback(() => {
  // 더 많은 데이터 로드 로직
}, [/* 필요한 의존성 */]);
src/components/common/Post/SubReply.tsx (2)

73-73: null 프로필 이미지 처리 개선 필요

creatorProfileImageUrl이 null일 때 빈 문자열 대신 undefined를 전달하는 것이 더 적절합니다.

-          creatorProfileImageUrl={creatorProfileImageUrl || ''}
+          creatorProfileImageUrl={creatorProfileImageUrl || undefined}

146-151: 중복된 스타일 제거 가능

.reply-nickname 스타일이 부모 .reply 스타일과 동일합니다. 별도 스타일이 필요하지 않다면 제거할 수 있습니다.

      .reply-nickname {
-        color: ${colors.grey[100]};
-        font-size: ${typography.fontSize.sm};
-        font-weight: ${typography.fontWeight.regular};
-        line-height: 20px;
+        display: inline;
      }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between c7242cc and 61a4b75.

⛔ Files ignored due to path filters (1)
  • src/assets/header/header-logo.svg is excluded by !**/*.svg
📒 Files selected for processing (22)
  • index.html (1 hunks)
  • src/api/comments/deleteComment.ts (1 hunks)
  • src/api/comments/getComments.ts (1 hunks)
  • src/api/comments/postLike.ts (1 hunks)
  • src/api/users/getUsers.ts (1 hunks)
  • src/components/common/Modal/PopupContainer.tsx (2 hunks)
  • src/components/common/Modal/ReplyModal.tsx (1 hunks)
  • src/components/common/Post/Reply.tsx (2 hunks)
  • src/components/common/Post/ReplyList.tsx (1 hunks)
  • src/components/common/Post/SubReply.tsx (3 hunks)
  • src/components/feed/UserProfileItem.tsx (4 hunks)
  • src/data/postData.ts (1 hunks)
  • src/hooks/useDebounce.ts (1 hunks)
  • src/hooks/usePopupActions.ts (2 hunks)
  • src/hooks/useUserSearch.ts (1 hunks)
  • src/pages/feed/FeedDetailPage.tsx (3 hunks)
  • src/pages/feed/UserSearch.tsx (2 hunks)
  • src/pages/feed/UserSearchResult.tsx (3 hunks)
  • src/stores/usePopupStore.ts (2 hunks)
  • src/types/post.ts (1 hunks)
  • src/types/profile.ts (0 hunks)
  • src/types/user.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • src/types/profile.ts
🧰 Additional context used
🧬 Code Graph Analysis (10)
src/hooks/usePopupActions.ts (1)
src/stores/usePopupStore.ts (1)
  • ReplyModalProps (28-37)
src/components/common/Modal/PopupContainer.tsx (1)
src/stores/usePopupStore.ts (1)
  • ReplyModalProps (28-37)
src/api/comments/deleteComment.ts (1)
src/api/index.ts (1)
  • apiClient (27-33)
src/pages/feed/UserSearch.tsx (2)
src/hooks/useUserSearch.ts (1)
  • useUserSearch (11-80)
src/pages/feed/UserSearchResult.tsx (1)
  • UserSearchResult (15-70)
src/hooks/useUserSearch.ts (2)
src/api/users/getUsers.ts (2)
  • UserData (3-10)
  • getUsers (26-42)
src/hooks/useDebounce.ts (1)
  • useDebounce (3-17)
src/components/common/Post/Reply.tsx (3)
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/getComments.ts (1)
src/api/index.ts (1)
  • apiClient (27-33)
src/api/comments/postLike.ts (1)
src/api/index.ts (1)
  • apiClient (27-33)
src/pages/feed/UserSearchResult.tsx (1)
src/api/users/getUsers.ts (1)
  • UserData (3-10)
src/api/users/getUsers.ts (1)
src/api/index.ts (1)
  • apiClient (27-33)
🔇 Additional comments (36)
index.html (1)

5-5: 파비콘 경로를 절대 경로로 변경한 점 확인 필요

href="/assets/custom_favicon.svg" 로 절대 경로를 사용하면 루트 도메인(/)가 배포 base path와 동일할 때만 정상 동작합니다.
만약 프리뷰·스토리북·서브 디렉터리 배포(예: https://example.com/app/)가 존재한다면 파비콘이 로드되지 않을 수 있으니, 다음 중 하나를 검토해 주세요.

- <link rel="icon" type="image/svg+xml" href="/assets/custom_favicon.svg" />
+ <!-- 1) 상대 경로로 유지 -->
+ <link rel="icon" type="image/svg+xml" href="./assets/custom_favicon.svg" />
+
+ <!-- 2) Vite 환경 변수(base URL)를 활용 -->
+ <link rel="icon" type="image/svg+xml" href="%VITE_BASE_URL%assets/custom_favicon.svg" />

배포 환경이 항상 루트에 고정되어 있는지 확인 부탁드립니다.

src/types/user.ts (1)

5-5: 네이밍 컨벤션 개선이 적절합니다.

nickNamenickname으로 변경하여 camelCase 네이밍 컨벤션을 일관되게 적용한 점이 좋습니다.

src/components/feed/UserProfileItem.tsx (2)

11-11: 프로퍼티명 변경이 타입 정의와 일치합니다.

nickNamenickname으로 변경하여 타입 정의와 일관성을 유지한 점이 좋습니다.


70-72: 반응형 디자인 개선이 적절합니다.

max-width, min-width, margin: 0 auto 추가로 반응형 레이아웃과 중앙 정렬이 잘 구현되었습니다.

src/hooks/useDebounce.ts (1)

1-17: 디바운스 훅 구현이 우수합니다.

제네릭 타입을 지원하며, cleanup 함수로 메모리 누수를 방지하는 표준적인 디바운스 로직이 잘 구현되어 있습니다. 사용자 검색 API 호출 최적화에 유용할 것입니다.

src/components/common/Modal/PopupContainer.tsx (3)

6-6: ReplyModal 임포트가 적절합니다.

새로운 모달 컴포넌트 임포트가 올바르게 추가되었습니다.


8-13: 타입 임포트가 일관되게 구현되었습니다.

ReplyModalProps 타입 임포트가 기존 패턴과 일관되게 추가되었습니다.


47-49: Wrapper 사용 여부를 확인해주세요.

다른 모달들과 달리 ReplyModal은 Wrapper를 사용하지 않습니다. 이것이 의도된 디자인인지 확인해주세요. ReplyModal이 자체적으로 포지셔닝을 처리하는 것인지 검토가 필요합니다.

src/hooks/usePopupActions.ts (3)

1-6: ReplyModalProps import 추가 승인

댓글 더보기 모달 기능을 위한 타입 import가 적절히 추가되었습니다.


23-26: openReplyModal 함수 구현 승인

기존 패턴과 일관성 있게 구현되었으며, 댓글 더보기 모달 기능이 올바르게 추가되었습니다.


32-32: 반환 객체에 openReplyModal 추가 승인

새로운 함수가 올바르게 export되어 다른 컴포넌트에서 사용할 수 있도록 구성되었습니다.

src/components/common/Post/ReplyList.tsx (1)

16-17: 데이터 구조 변경에 따른 프로퍼티명 업데이트 승인

replyCommentListreplyList, replyCommentIdreplyId로의 변경이 전체 데이터 모델 리팩토링과 일관성 있게 적용되었습니다.

src/pages/feed/UserSearch.tsx (4)

11-11: useUserSearch hook import 승인

실시간 사용자 검색 기능을 위한 hook이 적절히 import되었습니다.


19-23: useUserSearch hook 통합 승인

정적 mock 데이터에서 동적 API 데이터로의 전환이 올바르게 구현되었습니다. 디바운싱, 페이지네이션, 로딩 상태 관리가 잘 통합되어 있습니다.


92-96: UserSearchResult props 업데이트 승인 (searched 상태)

무한 스크롤 기능을 위한 새로운 props(loading, hasMore, onLoadMore)가 올바르게 전달되었습니다.


100-104: UserSearchResult props 업데이트 승인 (searching 상태)

검색 중 상태에서도 동일한 props가 일관성 있게 전달되어 무한 스크롤 기능이 지원됩니다.

src/pages/feed/FeedDetailPage.tsx (5)

13-13: getComments API import 승인

댓글 데이터를 동적으로 가져오기 위한 API가 적절히 import되었습니다.


19-19: commentList 상태 추가 승인

댓글 데이터를 관리하기 위한 상태가 올바르게 추가되었습니다.


28-32: cleanup effect 추가 승인

페이지를 떠날 때 답글 상태를 초기화하는 적절한 cleanup 로직이 추가되었습니다.


35-64: 병렬 데이터 로딩 구현 승인

Promise.all을 사용하여 피드 상세 정보와 댓글 목록을 효율적으로 병렬 로딩하는 구현이 훌륭합니다. 에러 처리와 로딩 상태 관리도 적절합니다.


128-128: ReplyList에 동적 댓글 데이터 전달 승인

정적 mock 데이터 대신 API에서 가져온 동적 댓글 데이터가 올바르게 전달되었습니다.

src/data/postData.ts (5)

89-93: 댓글 작성자 프로퍼티명 리팩토링 승인

댓글 작성자 관련 프로퍼티명이 일관된 명명 규칙(creator*)으로 통일되어 코드 가독성과 일관성이 향상되었습니다.


99-99: 답글 목록 프로퍼티명 변경 승인

replyCommentListreplyList로의 변경이 다른 컴포넌트들의 변경사항과 일관성 있게 적용되었습니다.


101-109: 첫 번째 답글 프로퍼티명 리팩토링 승인

답글 관련 프로퍼티들이 간결하고 일관된 명명 규칙으로 개선되었습니다 (replyCommentIdreplyId, replyCommentContentcontent 등).


113-121: 두 번째 답글 프로퍼티명 리팩토링 승인

동일한 명명 규칙이 일관성 있게 적용되어 전체 데이터 구조의 통일성이 확보되었습니다.


128-137: 두 번째 댓글 프로퍼티명 리팩토링 승인

모든 댓글에서 동일한 명명 규칙이 적용되어 데이터 구조의 일관성이 완벽하게 유지되었습니다.

src/hooks/useUserSearch.ts (3)

1-9: 임포트와 타입 정의가 적절함

필요한 의존성들이 올바르게 임포트되어 있고 인터페이스 정의도 명확합니다.


44-44: 페이지네이션 로직의 한계점 확인 필요

현재 setHasMore(newUserList.length === size) 로직은 PR 목표에서 언급된 것처럼, 정확히 size와 일치할 때 더 많은 데이터가 있는지 판단하기 어려운 한계가 있습니다. 서버에서 명시적인 hasMore 필드를 제공하지 않는 한 이는 불가피한 제약사항으로 보입니다.

서버 API 응답에서 명시적으로 hasMore나 totalCount 같은 필드를 제공하는지 확인해보세요.


60-64: loadMore 함수의 조건 검사가 적절함

로딩 상태, hasMore 플래그, 키워드 존재 여부를 모두 체크하여 불필요한 API 호출을 방지하고 있습니다.

src/api/users/getUsers.ts (2)

3-10: UserData 인터페이스 정의가 명확함

사용자 데이터 구조가 잘 정의되어 있고 필드명도 직관적입니다.


26-42: 쿼리 파라미터 구성 로직이 견고함

URLSearchParams를 사용한 쿼리 스트링 구성이 안전하고, 선택적 파라미터 처리도 적절합니다.

src/components/common/Modal/ReplyModal.tsx (1)

12-24: localStorage 접근에 대한 에러 핸들링이 적절함

try-catch 블록으로 localStorage 접근 오류를 적절히 처리하고 있습니다.

src/api/comments/getComments.ts (2)

3-28: 댓글과 답글 인터페이스 구조가 일관됨

CommentData와 ReplyData가 거의 동일한 필드를 가지고 있어 일관성이 있습니다. 공통 인터페이스를 추출하여 코드 중복을 줄일 수도 있지만, 향후 각각 다른 필드가 추가될 가능성을 고려하면 현재 구조도 적절합니다.


46-62: 커서 기반 페이지네이션 구현이 적절함

선택적 파라미터로 size와 cursor를 받아 쿼리 스트링을 구성하는 방식이 깔끔하고 안전합니다.

src/stores/usePopupStore.ts (1)

4-37: 팝업 타입 확장이 적절하게 구현됨

새로운 reply-modal 타입과 ReplyModalProps 인터페이스가 기존 패턴을 잘 따르고 있으며, 필요한 속성들이 모두 포함되어 있습니다.

src/types/post.ts (1)

41-68: 타입 정의 개선이 잘 이루어짐

프로퍼티 명명 규칙이 일관성 있게 개선되었고, nullable 타입 처리가 적절합니다. creatorProfileImageUrl을 nullable로 처리한 것과 parentCommentCreatorNickname을 옵셔널로 추가한 것이 실제 사용 케이스를 잘 반영합니다.

Comment on lines +3 to +5
export interface PostLikeRequest {
type: boolean;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

요청 인터페이스의 프로퍼티명이 모호합니다.

type: boolean보다는 isLike: boolean 또는 action: 'like' | 'unlike'가 더 명확합니다.

export interface PostLikeRequest {
-  type: boolean;
+  isLike: boolean;
}
📝 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
export interface PostLikeRequest {
type: boolean;
}
export interface PostLikeRequest {
isLike: boolean;
}
🤖 Prompt for AI Agents
In src/api/comments/postLike.ts lines 3 to 5, the property name 'type' in the
PostLikeRequest interface is ambiguous. Rename the property to a clearer name
such as 'isLike' with a boolean type or 'action' with a union type of 'like' or
'unlike' to better convey the intent of the request.

Comment on lines +7 to +15
export interface PostLikeResponse {
code: number;
status: string;
message: string;
data: {
commentId: number;
isLiked: boolean;
};
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

응답 인터페이스에 좋아요 개수가 누락되었습니다.

UI 컴포넌트에서 좋아요 개수를 업데이트하려면 likeCount 필드가 필요할 것으로 보입니다.

export interface PostLikeResponse {
  code: number;
  status: string;
  message: string;
  data: {
    commentId: number;
    isLiked: boolean;
+    likeCount: number;
  };
}
🤖 Prompt for AI Agents
In src/api/comments/postLike.ts between lines 7 and 15, the PostLikeResponse
interface is missing the likeCount field in the data object. Add a likeCount
property of type number to the data object to provide the UI component with the
updated count of likes.

Comment on lines +79 to +87
{isMyReply ? (
<OptionItem className="report">
<div className="option-text">신고하기</div>
</OptionItem>
) : (
<OptionItem onClick={handleDelete} className="delete">
<div className="option-text">삭제하기</div>
</OptionItem>
)}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

삭제/신고 옵션 로직이 반대로 구현됨

현재 코드에서 isMyReply가 true일 때 "신고하기"를, false일 때 "삭제하기"를 보여주고 있는데, 이는 일반적인 UX와 반대입니다. 내 댓글은 삭제할 수 있어야 하고, 다른 사람 댓글은 신고할 수 있어야 합니다.

다음과 같이 수정해야 합니다:

-          {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>
+          )}
📝 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
{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>
)}
🤖 Prompt for AI Agents
In src/components/common/Modal/ReplyModal.tsx between lines 79 and 87, the logic
for displaying "삭제하기" and "신고하기" options is reversed. When isMyReply is true, it
should show the "삭제하기" option with the onClick handler for deletion, and when
false, it should show the "신고하기" option without the delete handler. Swap the JSX
blocks inside the conditional rendering to fix this UX issue.

Comment on lines +34 to +35
console.log('좋아요 상태 변경 성공:', response);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

프로덕션 코드에서 console.log 제거 필요

디버깅용 console.log 문은 프로덕션 환경에서 제거되어야 합니다.

-      console.log('좋아요 상태 변경 성공:', response);
-
-      console.error('좋아요 상태 변경 실패:', error);
+      // TODO: 사용자에게 에러 알림 표시

Also applies to: 40-41

🤖 Prompt for AI Agents
In src/components/common/Post/Reply.tsx at lines 34-35 and 40-41, remove the
console.log statements used for debugging the like status change success and any
other console.log calls to ensure no debug logs remain in the production code.

Comment on lines +37 to +38
setLiked(response.data.isLiked);
setLikeCount(prev => (response.data.isLiked ? prev + 1 : prev - 1));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

좋아요 카운트 업데이트 로직 개선 고려

현재 로직은 서버의 isLiked 값에 따라 단순히 +1/-1을 하고 있습니다. 여러 사용자가 동시에 좋아요를 누를 경우 실제 카운트와 차이가 발생할 수 있습니다. 서버에서 업데이트된 실제 카운트를 반환받아 사용하는 것이 더 정확할 것입니다.

서버 API 응답에 실제 좋아요 카운트가 포함되어 있는지 확인이 필요합니다:


🏁 Script executed:

#!/bin/bash
# postLike API 응답 타입 확인
ast-grep --pattern 'interface PostLikeResponse {
  $$$
}'

Length of output: 495


서버 응답에 실제 좋아요 카운트 포함 검토 요청

현재 PostLikeResponse 타입에는 isLiked만 반환되므로, 클라이언트에서 아래와 같이 로컬 prev +1/-1 로직을 사용하고 있습니다.
동시성 상황에서 실제 데이터와 불일치가 발생할 수 있으니, 서버 API가 업데이트된 전체 likeCount를 반환하도록 확장한 뒤 해당 값을 직접 사용하도록 변경하는 것을 권장합니다.

  • 대상 파일: src/components/common/Post/Reply.tsx
  • 대상 라인: 37–38
- setLiked(response.data.isLiked);
- setLikeCount(prev => (response.data.isLiked ? prev + 1 : prev - 1));
+ setLiked(response.data.isLiked);
+ // 서버에서 받은 최신 좋아요 카운트로 직접 설정
+ setLikeCount(response.data.likeCount);

추가로, src/api/comments/postLike.tsPostLikeResponse.datalikeCount: number; 필드를 포함하도록 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
setLiked(response.data.isLiked);
setLikeCount(prev => (response.data.isLiked ? prev + 1 : prev - 1));
setLiked(response.data.isLiked);
// 서버에서 받은 최신 좋아요 카운트로 직접 설정
setLikeCount(response.data.likeCount);
🤖 Prompt for AI Agents
In src/components/common/Post/Reply.tsx at lines 37-38, the current code updates
likeCount locally by incrementing or decrementing based on isLiked, which can
cause inconsistencies in concurrent scenarios. Update the server API to include
the actual likeCount in PostLikeResponse, then modify the code to setLikeCount
directly from response.data.likeCount instead of adjusting it locally. Also,
update the PostLikeResponse type in src/api/comments/postLike.ts to include
likeCount: number to reflect this API contract change.

Comment on lines +39 to +40
const response = await postLike(replyId, !liked);
console.log('좋아요 상태 변경 성공:', response);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

프로덕션 코드에서 console.log 제거 필요

디버깅용 console.log 문은 프로덕션 환경에서 제거되어야 합니다.

-      console.log('좋아요 상태 변경 성공:', response);
-
-      console.error('좋아요 상태 변경 실패:', error);
+      // TODO: 사용자에게 에러 알림 표시

Also applies to: 46-46

🤖 Prompt for AI Agents
In src/components/common/Post/SubReply.tsx at lines 39-40 and line 46, remove
the console.log statements used for debugging the like status change. These logs
should not be present in production code to avoid unnecessary console output and
potential performance issues.

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