Skip to content

Feat/친구 추가, 삭제, 요청 목록 조회 기능 추가#12

Merged
aryu1217 merged 10 commits intomainfrom
feat/friend
Jan 15, 2026
Merged

Feat/친구 추가, 삭제, 요청 목록 조회 기능 추가#12
aryu1217 merged 10 commits intomainfrom
feat/friend

Conversation

@aryu1217
Copy link
Copy Markdown
Member

@aryu1217 aryu1217 commented Jan 14, 2026

Title

feat: 친구 기능 전체 구현

Summary

  • 친구 검색/요청/수락·거절/목록 조회/삭제까지 친구 관련 플로우 전반 구현

  • 각 기능을 FSD 기준으로 feature 단위로 분리하고, 버튼/카드 UI 연결 및 상태 처리(로딩/비활성) 반영

  • React Query 기반으로 서버 상태 갱신(invalidate 등) 포함하여 화면 동기화

Notes

  • 추후 백엔드 응답 변경 후 FriendCard.tsx 에서 slug -> nickname으로 수정 필요

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 친구 검색 및 친구 추가 요청 기능 추가
    • 받은 친구 요청 조회 및 수락 기능 추가
    • 친구 목록 조회 및 친구 삭제 기능 추가
    • 방 삭제 기능 추가
    • 사용자 검색 박스 및 결과 표시 기능 추가
  • 개선 사항

    • 방 목록 UI를 개선된 카드 형식으로 변경

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 14, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

새로운 친구 관리, 방 삭제, 사용자 검색 기능을 추가합니다. API 레이어, 타입 정의, React Query 훅, UI 컴포넌트를 일관된 패턴으로 구현하고 테스트 페이지 레이아웃을 재구성합니다.

Changes

Cohort / File(s) 변경 사항
친구 목록 기능
src/entities/friend/model/types.ts, src/features/friend/list/.../*
Friend, FriendsListResponse 타입 추가; fetchFriends API 함수 구현; useFriendsList React Query 훅 추가; FriendCard, FriendsList UI 컴포넌트 생성
친구 제거 기능
src/features/friend/remove/.../*
RemoveFriendParams 타입 정의; removeFriend API 함수 및 useRemoveFriend 훅 구현; RemoveFriendButton 컴포넌트 추가
친구 요청 관리
src/features/friend/requests/.../*
요청 관련 타입 5개 추가 (SendFriendRequestPayload, ReceivedFriendRequest 등); 4개 API 함수 구현 (sendFriendRequest, acceptFriendRequest, fetchReceivedFriendRequests); 3개 React Query 훅 추가; 4개 UI 컴포넌트 생성
방 삭제 기능
src/entities/room/api/deleteRoom.ts, src/entities/room/hooks/useDeleteRoom.ts, src/features/room/delete/ui/DeleteRoomButton.tsx
방 삭제 API 함수 구현; useDeleteRoom 훅 추가; DeleteRoomButton 컴포넌트 생성
방 카드 컴포넌트
src/entities/room/ui/RoomCard.tsx, src/features/room/list/ui/RoomListTest.tsx
RoomCard 컴포넌트 추가; RoomListTest 리팩토링하여 RoomCard 활용 및 DeleteRoomButton 통합
방 생성 업데이트
src/entities/room/api/createRoom.ts, src/features/room/create/ui/CreateRoomTest.tsx
createRoom 응답 처리 변경 (Location 헤더 → 응답 body); CreateRoomTest에서 다중 태그 선택 UI 추가 (MAX_TAGS: 5)
사용자 검색 기능
src/features/user/search/.../*
4개 타입 정의 (SearchUserParams, SearchUser, SearchUsersResponse, UserRelationship); searchUsers API 함수 구현; useSearchUsers 훅 추가; UserSearchBox, UserSearchCard, UserSearchList UI 컴포넌트 생성
사용자 프로필 경로 수정
src/features/user/profile/ui/NicknameEditForm.tsx
CheckNicknameButton 임포트 경로 변경 (user-profileuser/profile)
테스트 페이지 재구성
src/app/test/page.tsx
NicknameEditForm 임포트 경로 업데이트; FriendsList, RoomsListTest, UserSearchBox, FriendsRequestList 컴포넌트 추가; 2열 레이아웃 구조 적용; RoomTags 제거

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant FriendCard
    participant RemoveFriendButton
    participant useRemoveFriend Hook
    participant QueryClient
    participant API as /api/v1/friends/{slug}

    User->>FriendCard: 친구 카드 렌더링
    FriendCard->>RemoveFriendButton: 버튼 렌더링
    User->>RemoveFriendButton: 클릭
    RemoveFriendButton->>useRemoveFriend Hook: mutate(targetSlug)
    useRemoveFriend Hook->>API: DELETE 요청
    API-->>useRemoveFriend Hook: boolean 응답
    useRemoveFriend Hook->>QueryClient: ["friends"] 캐시 무효화
    QueryClient-->>FriendCard: 친구 목록 갱신
    FriendCard-->>User: 업데이트된 목록 표시
Loading
sequenceDiagram
    actor User
    participant UserSearchBox
    participant useSearchUsers Hook
    participant UserSearchList
    participant API as /api/v1/user-profiles

    User->>UserSearchBox: 검색어 입력
    UserSearchBox->>useSearchUsers Hook: params.query 전달
    useSearchUsers Hook->>API: GET 요청
    API-->>useSearchUsers Hook: SearchUsersResponse 반환
    useSearchUsers Hook-->>UserSearchList: 검색 결과 전달
    UserSearchList->>UserSearchList: SearchUser[] 매핑
    UserSearchList-->>User: UserSearchCard 목록 렌더링
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

이유: 광범위한 파일 분산 (30개 이상 신규 파일), 일관된 패턴의 반복 구현 (API → 타입 → 훅 → UI), 친구 관리, 방 삭제, 사용자 검색 등 여러 도메인을 포괄하는 다양한 기능 추가. 각 기능별로 타입 안정성, API 응답 처리, React Query 설정, UI 상태 관리를 세밀하게 검토해야 하며, 테스트 페이지 레이아웃 변경도 함께 검토 필요.

Possibly related PRs

Poem

🐰 친구도 찾고, 방도 지우고,
검색창에서 사용자를 부르네요.
React Query가 캐시를 춤추고,
API가 응답해 주는 화합으로,
새로운 기능들이 꽃피네요! 🌸

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 친구 추가, 삭제, 요청 목록 조회 기능의 핵심 내용을 명확하게 요약하고 있으며, 변경 사항의 주요 부분을 정확히 반영합니다.
Description check ✅ Passed PR 설명이 Summary와 Notes 섹션을 포함하여 필수 정보를 대부분 제공하지만, 템플릿의 Linked Issue와 Checklist 섹션이 작성되지 않았습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

🧹 Recent nitpick comments
src/features/friend/list/ui/FriendsList.tsx (1)

7-7: size 값을 props로 받는 것을 고려해보세요.

현재 size: 20이 하드코딩되어 있습니다. 재사용성을 위해 props로 받거나 기본값을 설정하는 방식을 고려해볼 수 있습니다.

♻️ 선택적 개선안
-export default function FriendsList() {
-  const { data, isLoading, isError } = useFriendsList({ size: 20 });
+interface FriendsListProps {
+  size?: number;
+}
+
+export default function FriendsList({ size = 20 }: FriendsListProps) {
+  const { data, isLoading, isError } = useFriendsList({ size });
src/entities/friend/model/types.ts (1)

1-6: 타입 정의가 명확합니다.

도메인 타입이 잘 정의되어 있습니다. 선택적으로 id 필드에 대해 브랜드 타입을 사용하면 타입 안전성을 높일 수 있습니다.

💡 선택적 개선안 (브랜드 타입)
// 다른 엔티티의 id와 혼동을 방지하려면 브랜드 타입 사용 고려
export type FriendId = number & { readonly __brand: "FriendId" };

export type Friend = {
  id: FriendId;
  nickname: string;
  slug: string;
  profileImageUrl: string;
};
src/features/friend/remove/hooks/useRemoveFriend.ts (1)

13-13: mutationFn 직접 참조로 단순화 가능

파라미터를 그대로 전달하는 래퍼 함수는 직접 함수 참조로 대체할 수 있습니다.

♻️ 제안 코드
-    mutationFn: (params) => removeFriend(params),
+    mutationFn: removeFriend,
src/features/friend/requests/hooks/useFetchReceivedFriendRequest.ts (1)

11-12: 함수명 단수/복수 일관성 검토 권장

함수명이 useFetchReceivedFriendRequest (단수)인데, 반환 타입은 ReceivedFriendRequestsResponse (복수, 여러 요청 목록)입니다. 일관성을 위해 useFetchReceivedFriendRequests로 변경하는 것을 고려해 주세요.

제안 diff
-export function useFetchReceivedFriendRequest(
+export function useFetchReceivedFriendRequests(
   params?: FetchReceivedFriendRequestsParams
 ) {

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e35dbb0 and 6cfcc11.

📒 Files selected for processing (12)
  • src/entities/friend/model/types.ts
  • src/entities/room/api/deleteRoom.ts
  • src/entities/room/ui/RoomCard.tsx
  • src/features/friend/list/ui/FriendCard.tsx
  • src/features/friend/list/ui/FriendsList.tsx
  • src/features/friend/remove/api/removeFriend.ts
  • src/features/friend/remove/hooks/useRemoveFriend.ts
  • src/features/friend/requests/api/fetchReceivedFriendRequests.ts
  • src/features/friend/requests/api/sendFriendRequest.ts
  • src/features/friend/requests/hooks/useFetchReceivedFriendRequest.ts
  • src/features/friend/requests/ui/FriendsReceivedRequestList.tsx
  • src/features/user/search/ui/UserSearchCard.tsx
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/features/friend/list/ui/FriendCard.tsx
  • src/entities/room/ui/RoomCard.tsx
  • src/features/user/search/ui/UserSearchCard.tsx
  • src/features/friend/requests/ui/FriendsReceivedRequestList.tsx
  • src/features/friend/requests/api/sendFriendRequest.ts
  • src/features/friend/remove/api/removeFriend.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*

⚙️ CodeRabbit configuration file

**/*: - 리뷰는 한국어로 작성.

  • 유지보수/확장성 관점에서 모듈 경계(의존성 방향, 책임 분리)가 적절한지 최우선으로 확인.
  • 파일/함수 책임이 과도하면 응집도/결합도 기준으로 분리/통합 개선안을 제시.
  • TypeScript에서 any/과도한 as 캐스팅을 지양하고, 도메인 타입(예: ProfileId 같은 ID 타입)으로 의미를 드러내는지 확인.
  • React에서 상태 위치 미스, 불필요한 리렌더링, derived state 남발, useEffect 남용을 지적하고 개선안을 제시.

Files:

  • src/features/friend/remove/hooks/useRemoveFriend.ts
  • src/features/friend/requests/hooks/useFetchReceivedFriendRequest.ts
  • src/features/friend/requests/api/fetchReceivedFriendRequests.ts
  • src/entities/friend/model/types.ts
  • src/entities/room/api/deleteRoom.ts
  • src/features/friend/list/ui/FriendsList.tsx
**/api/**/*.ts*

⚙️ CodeRabbit configuration file

**/api/**/*.ts*: - API 모듈은 DTO 타입 + 네트워크 호출 + toDomain 매핑까지 책임지는 구조를 우선 권장.

  • UI에서 raw DTO(res.json())를 직접 다루지 않도록, 반환 타입은 Domain(User 등)으로 정규화.
  • fetch 옵션(credentials, headers 등) 규약이 일관적인지 확인.

Files:

  • src/features/friend/requests/api/fetchReceivedFriendRequests.ts
  • src/entities/room/api/deleteRoom.ts
**/ui/**/*.tsx

⚙️ CodeRabbit configuration file

**/ui/**/*.tsx: - 상태 위치가 적절한지(상/하위 이동 필요성) 지적하고 개선안 제시.

  • 파생 값은 state로 두지 말고 계산(useMemo는 필요할 때만).
  • 리스트 key 안정성, 불필요한 re-render 유발 props(익명 함수/객체) 지적.

Files:

  • src/features/friend/list/ui/FriendsList.tsx
🧬 Code graph analysis (5)
src/features/friend/remove/hooks/useRemoveFriend.ts (3)
src/shared/api/api-error.ts (1)
  • ApiError (1-11)
src/features/friend/remove/model/types.ts (1)
  • RemoveFriendParams (1-3)
src/features/friend/remove/api/removeFriend.ts (1)
  • removeFriend (5-13)
src/features/friend/requests/hooks/useFetchReceivedFriendRequest.ts (3)
src/features/friend/requests/model/types.ts (2)
  • FetchReceivedFriendRequestsParams (19-22)
  • ReceivedFriendRequestsResponse (14-17)
src/shared/api/api-error.ts (1)
  • ApiError (1-11)
src/features/friend/requests/api/fetchReceivedFriendRequests.ts (1)
  • fetchReceivedFriendRequests (8-16)
src/features/friend/requests/api/fetchReceivedFriendRequests.ts (3)
src/features/friend/requests/model/types.ts (2)
  • FetchReceivedFriendRequestsParams (19-22)
  • ReceivedFriendRequestsResponse (14-17)
src/shared/api/axiosInstance.ts (1)
  • axiosInstance (5-12)
src/shared/api/types.ts (1)
  • ApiResponse (1-1)
src/entities/room/api/deleteRoom.ts (2)
src/shared/api/axiosInstance.ts (1)
  • axiosInstance (5-12)
src/shared/api/types.ts (1)
  • ApiResponse (1-1)
src/features/friend/list/ui/FriendsList.tsx (2)
src/features/friend/list/hooks/useFriendsList.ts (1)
  • useFriendsList (7-13)
src/features/friend/list/ui/FriendCard.tsx (1)
  • FriendCard (6-18)
🔇 Additional comments (7)
src/entities/room/api/deleteRoom.ts (1)

1-9: LGTM!

이전 리뷰에서 지적된 encodeURIComponent 적용이 반영되었고, 구현이 깔끔합니다.

  • 공유 axiosInstanceApiResponse 타입을 사용하여 프로젝트 규약과 일관성 유지
  • 단순 boolean 반환이므로 별도의 Domain 변환 없이 res.data.result 직접 반환은 적절함
  • 경로 파라미터 인코딩으로 특수 문자 처리 안전성 확보
src/features/friend/list/ui/FriendsList.tsx (1)

6-31: 구현이 잘 되어있습니다.

  • 상태 위치가 적절합니다 (useFriendsList 훅에서 관리).
  • friends는 파생 값으로 state가 아닌 계산으로 처리되어 있습니다.
  • 리스트 key로 slug를 사용하여 안정적입니다.
  • 익명 함수/객체를 props로 전달하지 않아 불필요한 리렌더링이 없습니다.
src/entities/friend/model/types.ts (1)

8-11: 페이지네이션 응답 구조가 적절합니다.

hasNext 필드가 커서 기반 페이지네이션을 지원하며, useFriendsList 훅의 lastId 파라미터와 잘 연동됩니다.

src/features/friend/remove/hooks/useRemoveFriend.ts (2)

3-6: LGTM! type-only import 적용 완료

ApiErrorRemoveFriendParams 모두 type-only import로 적절히 변경되었습니다. 런타임 번들에 불필요한 import가 포함되지 않도록 하는 좋은 패턴입니다.


8-18: LGTM! 훅 구현 및 캐시 무효화 전략 적절

  • FSD 패턴에 맞는 모듈 경계와 책임 분리가 잘 되어 있습니다.
  • React Query의 prefix matching을 활용한 ["friends"], ["searchUsers"] 무효화로 친구 삭제 후 관련 UI가 올바르게 갱신됩니다.
  • 제네릭 타입 <boolean, ApiError, RemoveFriendParams> 명시로 타입 안전성이 확보되었습니다.
src/features/friend/requests/api/fetchReceivedFriendRequests.ts (1)

1-16: LGTM! API 모듈 구조가 잘 구성되어 있습니다.

  • axiosInstance를 통해 공통 설정(credentials, headers, XSRF 등)이 일관되게 적용됨
  • ApiResponse<T> 래퍼를 사용하여 타입 안전하게 응답을 처리하고, res.data.result로 언래핑하여 도메인 타입만 반환
  • 파일명 오타가 수정되었습니다 (RecivedReceived)
src/features/friend/requests/hooks/useFetchReceivedFriendRequest.ts (1)

1-23: LGTM! 이전 리뷰 피드백이 잘 반영되었습니다.

  • ApiError@/src/shared/api/api-error에서 import하여 서버 모듈 의존성 문제 해결
  • queryKey"recived" 오타가 "received"로 수정되어 캐시 무효화 정상 동작 보장

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @aryu1217, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 PR은 애플리케이션에 포괄적인 친구 관리 기능을 도입하고, 기존 방 생성 기능을 개선하며 방 삭제 기능을 추가합니다. 또한, 사용자 검색 기능을 통해 다른 사용자를 찾고 친구 요청을 보낼 수 있도록 합니다. 이러한 변경 사항들은 사용자 상호작용을 풍부하게 하고, 전반적인 코드 구조를 개선하여 유지보수성을 높이는 데 중점을 둡니다.

Highlights

  • 친구 관리 기능 추가: 친구 목록 조회, 친구 요청 보내기 및 수락, 친구 삭제 기능을 위한 API 연동 및 UI 컴포넌트가 추가되었습니다.
  • 방 관리 기능 개선: 방 생성 시 태그를 선택할 수 있는 UI가 추가되었으며, 방 삭제 기능이 구현되었습니다.
  • 사용자 검색 기능 도입: 사용자를 검색하고 친구 요청을 보낼 수 있는 사용자 검색 박스 및 관련 컴포넌트가 추가되었습니다.
  • 코드 구조 리팩토링: 기존 user-profile 관련 파일들이 user/profile로 재구성되었고, RoomListTest 컴포넌트가 features/room/list/ui로 이동되었습니다.
  • 테스트 페이지 업데이트: 새로 추가된 친구 및 사용자 검색 기능을 테스트할 수 있도록 src/app/test/page.tsx가 업데이트되었습니다.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

이번 PR은 친구 관련 기능(목록, 검색, 추가/삭제, 요청)을 대거 추가하는 내용이네요. 전반적으로 기능별로 코드가 잘 구조화되어 있고, React Query를 일관되게 사용하여 데이터 페칭 및 상태 관리를 잘 처리하고 있습니다. 몇몇 리팩토링을 통해 프로젝트 구조도 개선된 점이 좋습니다. 다만, 리뷰에서 지적한 몇 가지 치명적인 오류와 오타, 디버그용 코드 등이 있으니 머지 전에 꼭 수정이 필요해 보입니다. 특히 백엔드와의 데이터 불일치 문제와 잘못된 import는 신중하게 다루어야 할 부분입니다.

Comment thread src/features/friend/requests/hooks/useFetchRecivedFriendRequest.ts Outdated
Comment thread src/features/friend/list/ui/FriendCard.tsx
Comment thread src/features/friend/list/ui/FriendsList.tsx Outdated
Comment thread src/features/friend/requests/api/fetchReceivedFriendRequests.ts
Comment thread src/features/friend/requests/hooks/useFetchRecivedFriendRequest.ts Outdated
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: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/features/room/list/ui/RoomListTest.tsx (1)

3-49: 테스트 페이지(/test)의 프로덕션 노출 방지 및 인라인 props 최적화 필요

/test 라우트는 현재 환경 플래그나 인증 가드 없이 접근 가능하며, RoomsListTestDeleteRoomButton이 의도치 않게 프로덕션에 노출될 수 있습니다. 테스트용 UI는 배포 번들에서 제외하거나 개발 환경에만 노출되도록 별도 가드를 추가해야 합니다.

또한 RoomCard에 전달되는 actions prop이 매번 새로운 객체로 생성되고 있습니다. 리스트 렌더링 시 인라인 JSX 객체/함수를 피하고, 필요하면 useMemo로 감싸거나 컴포넌트 외부에서 정의하세요.

src/app/test/page.tsx (1)

1-16: production에서 /test 페이지 노출을 반드시 차단하세요 (DeleteRoomButton, CreateRoomTest 등 테스트 기능 위험)

이 페이지에는 DeleteRoomButton이 실제로 포함되어 있어(RoomsListTest의 각 방 옆에 표시) production 배포 시 사용자가 실제 방을 삭제할 수 있는 상황이 발생합니다. 또한 CreateRoomTest로도 테스트용 방이 대량 생성될 수 있습니다.

Server Component로 전환하여 notFound() 가드를 추가하는 것이 권장됩니다:

제안 diff
-"use client";
-
 import GoogleLoginButton from "@/src/features/auth/login-with-google/ui/googleLoginButton";
 import LogoutButton from "@/src/features/auth/logout/ui/logoutButton";
 import NicknameEditForm from "@/src/features/user/profile/ui/NicknameEditForm";
-import { useRouter } from "next/navigation";
+import { notFound } from "next/navigation";
 import IsLogin from "@/src/entities/user/ui/IsLogin";
 import CreateRoomTest from "@/src/features/room/create/ui/CreateRoomTest";
 import FriendsList from "@/src/features/friend/list/ui/FriendsList";
 import RoomsListTest from "@/src/features/room/list/ui/RoomListTest";
 import UserSearchBox from "@/src/features/user/search/ui/UserSearchBox";
 import FriendsRequestList from "@/src/features/friend/requests/ui/FriendsReceivedRequestList";

 export default function TestPage() {
-  const router = useRouter();
+  if (process.env.NODE_ENV === "production") notFound();

   return (
     <div className="bg-white p-4 space-y-4">
       <GoogleLoginButton />
       <LogoutButton />
@@
-      <button
-        onClick={() => router.push("/home")}
-        className="border cursor-pointer"
-      >
-        go to home page
-      </button>
+      <a href="/home" className="border cursor-pointer inline-block px-2 py-1">
+        go to home page
+      </a>
     </div>
   );
 }

주의: IsLogin, NicknameEditForm 등 모든 child 컴포넌트가 이미 client component("use client" 지정 또는 hooks 사용)이므로, Server Component 전환 시에도 정상 작동합니다. 다만 router.push()는 <a> 태그로 대체할 수 있는 간단한 네비게이션만 가능합니다. 더 복잡한 라우팅이 필요하면 child component에서 처리하세요.

🤖 Fix all issues with AI agents
In `@src/entities/room/api/deleteRoom.ts`:
- Around line 4-9: The deleteRoom function builds the request URL using the raw
slug which can contain characters like /, ?, #, % and break URL parsing or cause
security issues; update the URL construction in deleteRoom to pass slug through
encodeURIComponent (e.g., use `/api/v1/rooms/${encodeURIComponent(slug)}`) so
the path parameter is safely encoded before calling axiosInstance.delete and
returning res.data.result.

In `@src/entities/room/ui/RoomCard.tsx`:
- Around line 1-8: The file declares a local RoomTag type and uses
React.ReactNode without importing React which will cause TS errors; replace the
local RoomTag with the canonical type from src/entities/room/model/types by
adding an import like `import type { RoomTag } from
'src/entities/room/model/types'` and import the React node type instead of
referencing the namespace (e.g., `import type { ReactNode } from 'react'`) then
update the Props type to use ReactNode and the imported RoomTag; ensure the
identifiers referenced are Props, RoomTag, and React.ReactNode in RoomCard.tsx
so the compiler resolves types correctly.

In `@src/features/friend/list/model/types.ts`:
- Around line 1-4: FetchFriendsParams.lastId is typed as number but Friend.id is
a string, causing a type mismatch for cursor pagination; update
FetchFriendsParams (the FetchFriendsParams type and its lastId property) to use
string (or a shared domain alias like FriendId) so it matches the Friend.id
type, and update any callers to pass/handle string IDs accordingly.

In `@src/features/friend/list/ui/FriendCard.tsx`:
- Around line 6-16: The FriendCard UI is using swapped backend fields (passing
friend.nickname into RemoveFriendButton as targetSlug), which breaks domain
meaning; move the swap into the API/adapter layer by normalizing results in
fetchFriends so the returned Friend objects always have the correct slug and
nickname. Update fetchFriends (or its response mapping) to map backendSlug->slug
and backendNickname->nickname (or swap fields accordingly) so FriendCard can
pass RemoveFriendButton targetSlug={friend.slug} without special-casing; leave
UI components (FriendCard, RemoveFriendButton) unchanged and rely on normalized
data from fetchFriends.

In `@src/features/friend/remove/api/removeFriend.ts`:
- Around line 5-11: The removeFriend function lacks an explicit return type;
update its signature to return Promise<boolean> (i.e., declare removeFriend(...)
: Promise<boolean>) to match other API helpers like
sendFriendRequest/acceptFriendRequest and ensure type stability; optionally, if
you prefer defensive encoding, wrap targetSlug with encodeURIComponent when
building the URL (but encoding is not required per existing similar APIs).

In `@src/features/friend/remove/hooks/useRemoveFriend.ts`:
- Around line 3-18: 현재 useRemoveFriend에서 제네릭으로만 사용하는 RemoveFriendParams를 일반
import로 가져오고 있으므로 런타임 번들에 불필요하게 포함될 수 있습니다; import 문에서 RemoveFriendParams를 타입
전용으로 변경하세요 (즉, RemoveFriendParams를 type-only import로 바꿔서 불필요한 런타임 import를 방지하고
컴파일러 설정과 호환되게 만듭니다), 대상 식별자는 파일의 RemoveFriendParams와 useRemoveFriend 함수입니다.

In `@src/features/friend/requests/api/sendFriendRequest.ts`:
- Around line 6-14: The sendFriendRequest function assumes res.data.result
exists; add explicit response validation and handling: after calling
axiosInstance.post in sendFriendRequest, check HTTP status (handle 204 No
Content) and verify res.data is an object and typeof res.data.result ===
"boolean" before returning it; if validation fails, either throw a descriptive
error or return a safe default (e.g., false) and document the contract in a
comment referencing ApiResponse<boolean>, sendFriendRequest, and axiosInstance
so future callers know the expected backend shape.

In `@src/features/friend/requests/hooks/useFetchRecivedFriendRequest.ts`:
- Around line 15-21: The queryKey in useFetchRecivedFriendRequest (queryKey:
["friendRequests","recived", ...]) has a spelling typo ("recived") that can
prevent cache invalidation; change it to "received" and ideally centralize the
key by using a shared constant (e.g., FRIEND_REQUESTS_KEY or a tuple factory)
used by useFetchRecivedFriendRequest and any invalidateQueries callers so both
use the exact same identifier; update any references to the old key string to
the new constant to ensure invalidation (e.g., where accept/reject handlers call
invalidateQueries).
- Around line 3-14: Replace the server-side ApiError import with the project's
shared error type: remove the import from "next/dist/server/api-utils" and
import the shared ApiError (e.g., from "src/shared/api/api-error" or the
project's shared path) and update the generic on useQuery in
useFetchRecivedFriendRequest to use that shared ApiError type so the client hook
no longer depends on next/dist/server.

In `@src/features/friend/requests/ui/FriendsReceivedRequestList.tsx`:
- Around line 26-30: The list currently renders FriendRequestCard directly under
a <ul>, breaking semantics/accessibility; update FriendsReceivedRequestList to
place each FriendRequestCard inside an <li> (e.g., map to <li
key={item.requestId}><FriendRequestCard item={item} /></li>) or alternatively
change FriendRequestCard’s root element to an <li> and keep the existing
mapping; ensure keys remain on the <li> and that any styling/props on
FriendRequestCard still apply.
🧹 Nitpick comments (31)
src/features/user/profile/ui/NicknameEditForm.tsx (1)

9-48: 닉네임 변경 시 중복체크 결과(stale) 무효화/제출 조건을 명확히 하는 게 안전합니다.
현재는 nickname이 바뀌어도 isAvailable이 유지될 수 있고, isAvailable !== true여도 제출이 가능합니다(의도라면 OK).

제안 diff
-import { useState } from "react";
+import { useEffect, useState } from "react";
 import { useUpdateMe } from "../hooks/useUpdateMe";
 import CheckNicknameButton from "@/src/features/user/profile/ui/CheckNicknameButton";

 export default function NicknameEditForm() {
   const { mutate, isPending, error } = useUpdateMe();
   const [nickname, setNickname] = useState("");
   const [isAvailable, setIsAvailable] = useState<boolean | null>(null);
+  useEffect(() => {
+    setIsAvailable(null);
+  }, [nickname]);

   const onSubmit = (e: React.FormEvent) => {
     e.preventDefault();

     const trimmed = nickname.trim();
     if (!trimmed) return;

     mutate({ nickname: trimmed });
   };
@@
       <button
         type="submit"
         className="border px-3 py-1 cursor-pointer"
-        disabled={isPending}
+        disabled={isPending || isAvailable !== true}
       >
         {isPending ? "변경 중..." : "변경하기"}
       </button>
src/features/friend/remove/model/types.ts (1)

1-3: API/훅/UI 간 파라미터 타입 공유 👍
다만 targetSlug: string은 추후 UserSlug 같은 도메인 타입 별칭(브랜딩)으로 의미를 더 드러내면 확장/안전성에 도움이 됩니다.

src/entities/room/api/createRoom.ts (1)

5-18: 응답 타입 중복 제거 + slug 누락 가드 추가를 권장합니다.
지금은 res.data.result.slug가 없으면 { slug: undefined }가 반환될 수 있습니다.

제안 diff
 import { axiosInstance } from "@/src/shared/api/axiosInstance";
 import type { ApiResponse } from "@/src/shared/api/types";
 import type { CreateRoomPayload, CreateRoomResult } from "./types";

-type CreateRoomResponse = ApiResponse<{ slug: string }>;
+type CreateRoomResponse = ApiResponse<CreateRoomResult>;

 export async function createRoom(
   payload: CreateRoomPayload
 ): Promise<CreateRoomResult> {
   const res = await axiosInstance.post<CreateRoomResponse>(
     "/api/v1/rooms",
     payload
   );

   const slug = res.data.result.slug;
+  if (!slug) {
+    throw new Error("createRoom: missing slug in response");
+  }

   return { slug };
 }
src/features/friend/remove/ui/RemoveFriendButton.tsx (1)

3-17: 친구 삭제 UX: 확인(confirm) + 실패(error) 처리 경로를 UI에 노출 권장

지금은 isPending만 반영되고 실패 시 사용자가 알 수 있는 경로가 없습니다(네트워크/권한/서버 오류). 최소한 onError 토스트/인라인 메시지 또는 확인 다이얼로그(실수 방지)를 추가하는 게 좋아요. 추가로 RemoveFriendParams는 type-only import 권장, onClick 인라인 함수는 필요 시 핸들러로 분리해 props 변경/리렌더 노이즈를 줄일 수 있습니다.

Proposed fix (type-only import + click handler 분리 예시)
 import { useRemoveFriend } from "../hooks/useRemoveFriend";
-import { RemoveFriendParams } from "../model/types";
+import type { RemoveFriendParams } from "../model/types";

 export default function RemoveFriendButton({ targetSlug }: RemoveFriendParams) {
   const { mutate, isPending } = useRemoveFriend();
+  const handleClick = () => {
+    // 필요 시: if (!confirm("정말 친구를 삭제할까요?")) return;
+    mutate({ targetSlug });
+  };

   return (
     <button
       type="button"
       className="border cursor-pointer"
-      onClick={() => mutate({ targetSlug })}
+      onClick={handleClick}
       disabled={isPending}
     >
       {isPending ? "삭제중..." : "친구 삭제"}
     </button>
   );
 }
src/features/room/create/ui/CreateRoomTest.tsx (2)

5-46: React.FormEvent 참조/trim 중복/빈 tags 전송 형태는 한 번 정리하는 게 안전

  • Line 33: React.FormEvent는 프로젝트 설정에 따라 React 식별자가 스코프에 없어서 TS 에러가 날 수 있어 FormEvent를 type import해서 쓰는 편이 안전합니다.
  • Line 41: password.trim()이 2번 호출됩니다(작지만 불필요).
  • Line 42: tags: selectedTagSlugs는 항상 배열을 보냅니다. 서버가 “미지정”을 undefined로 기대하는 경우가 있어, 빈 배열 전송이 허용되는지 확인해 주세요.
Proposed fix (FormEvent type import + trim 1회)
-import { useState } from "react";
+import { useState } from "react";
+import type { FormEvent } from "react";
 import { useCreateRoom } from "@/src/features/room/create/model/useCreateRoom";
 import { useRoomTags } from "@/src/entities/room/hooks/useRoomTags";

@@
-  const onSubmit = (e: React.FormEvent) => {
+  const onSubmit = (e: FormEvent<HTMLFormElement>) => {
     e.preventDefault();

     const t = title.trim();
     if (!t) return;
+    const pw = password.trim();

     mutate({
       title: t,
-      password: password.trim() ? password.trim() : undefined,
+      password: pw ? pw : undefined,
       tags: selectedTagSlugs,
     });
   };

75-113: 태그 선택 버튼 접근성/표시값(슬러그 vs 이름) 개선 여지

현재 “선택됨” 영역은 slug를 그대로 노출합니다(Line 109-112). 테스트 UI라면 괜찮지만, 사람이 읽는 용도면 tag name으로 보여주는 편이 유용합니다. 또한 토글 버튼은 aria-pressed를 주면 선택 상태를 더 명확히 표현할 수 있어요.

src/features/user/search/model/types.ts (1)

1-19: 도메인 타입 관점에서 id: number는 브랜딩(또는 명명)으로 의미를 더 드러내는 편 추천

가이드라인 관점에서 id: number는 의미가 약해서(ProfileId/UserId 등) 최소한 별칭/브랜딩으로 의도를 드러내는 게 좋아요. 또한 UserRelationship"NONE" | "FRIEND"로 고정되어 있는데, 서버가 추후 "REQUESTED", "RECEIVED", "BLOCKED" 같은 상태를 추가할 가능성이 있어 스펙을 확인하고 확장 가능한 형태로 맞추는 걸 권장합니다. User도 type-only import가 안전합니다.

Proposed fix (type-only import + branded id 예시)
-import { User } from "@/src/entities/user/model/types";
+import type { User } from "@/src/entities/user/model/types";

+declare const userIdBrand: unique symbol;
+export type UserId = number & { readonly [userIdBrand]: void };
+
 export type UserRelationship = "NONE" | "FRIEND";

 export type SearchUser = User & {
-  id: number;
+  id: UserId;
   relationship: UserRelationship;
 };
src/features/user/search/hooks/useSearchUsers.ts (1)

6-12: queryKey/queryFn에서 query 정규화(trim) 일관성 맞추기
Line 8-10에서 enabled는 trim 기준인데, queryKey와 실제 요청은 raw params.query를 사용해서 "a" vs "a "가 다른 캐시/요청으로 취급될 수 있어요. const query = params.query.trim()으로 정규화 후 queryKeysearchUsers 입력에 동일하게 쓰는 쪽을 권장합니다.
또한 검색은 입력 변화가 잦아서, 가능하면 queryFn에서 React Query의 signalsearchUsers로 전달해 이전 요청을 취소(abort)할 수 있게 해두면 UX/트래픽에 유리합니다.

제안 diff
 export function useSearchUsers(params: SearchUserParams) {
+  const query = params.query.trim();
   return useQuery<SearchUsersResponse, ApiError>({
-    queryKey: ["searchUsers", params.query, params.lastId, params.limit],
-    queryFn: () => searchUsers(params),
-    enabled: params.query.trim().length > 0,
+    queryKey: ["searchUsers", query, params.lastId, params.limit],
+    queryFn: ({ signal }) => searchUsers({ ...params, query }, { signal }),
+    enabled: query.length > 0,
     retry: false,
   });
 }
src/features/friend/requests/model/types.ts (1)

1-26: ID/날짜 필드에 의미 있는 타입 별칭(또는 브랜디드 타입) 도입 고려
requestId, requesterId, createdAt가 모두 number|string 원시 타입이라 다른 ID/문자열과 섞여도 컴파일 타임에 잡기 어렵습니다. 최소한 type FriendRequestId = number, type UserId = number, type IsoDateTimeString = string 같은 별칭을 두고 여기서 사용하거나(더 좋게는) API 모듈에서 createdAtDate로 변환해 도메인 타입으로 올리는 구조를 추천합니다. (코딩 가이드라인의 “도메인 타입으로 의미 드러내기” 기준)

src/features/friend/requests/hooks/useSendFriendRequest.ts (2)

8-16: invalidateQueries(["searchUsers"])로 인한 피처 간 결합도 줄이기(키 상수화 추천)
친구요청 훅이 ["searchUsers"]를 직접 알고 있어서 friend/requests → user/search로 결합이 생깁니다. src/features/user/search/model/queryKeys.ts 같은 곳에 searchUsersQueryKey(...)(또는 SEARCH_USERS_QUERY_KEY)를 export하고 여기서는 그 상수를 사용하면 변경에 강해져요.
추가로 Line 13은 mutationFn: sendFriendRequest로 단순화 가능합니다.

제안 diff(개념)
   return useMutation<boolean, ApiError, SendFriendRequestPayload>({
     mutationKey: ["friendRequests", "send"],
-    mutationFn: (payload) => sendFriendRequest(payload),
-    onSuccess: () => qc.invalidateQueries({ queryKey: ["searchUsers"] }),
+    mutationFn: sendFriendRequest,
+    onSuccess: () => qc.invalidateQueries({ queryKey: ["searchUsers"] }),
   });

18-18: TODO는 이슈로 트래킹 권장
“보낸 친구요청 목록”은 범위가 커질 수 있어서, 코멘트로 남기기보다 이슈/태스크로 분리해 스펙(페이징 키, 응답 타입, UI/캐시 전략) 확정 후 진행하는 게 좋아요.

src/features/friend/requests/api/acceptFriendRequest.ts (1)

5-13: 코드베이스 전체 API 패턴 일관성 고려하여 가드 추가 검토 필요

현재 acceptFriendRequest와 모든 friend API(fetchFriends, removeFriend, sendFriendRequest 등)가 동일하게 res.data.result를 직접 반환하는 패턴을 사용 중입니다. ApiResponse<T> = { result: T } 스펙이 명확하고 axiosInstance 응답 인터셉터가 에러를 처리하고 있어 현재 상태는 안전합니다.

다만 타입 단언만으로는 런타임 검증이 없다는 점은 여전히 유효합니다. 응답 가드를 추가한다면, 일관성 있게 모든 API에 동일하게 적용하거나 공통 유틸 함수로 추출하는 것을 권장합니다. 단일 API에만 추가하면 패턴 일관성이 깨집니다.

src/features/friend/requests/hooks/useAcceptFriendRequest.ts (1)

8-19: 쿼리키 상수화로 invalidate 누락 리스크를 줄이세요

["friendRequests", "received"] / ["friends"] / ["searchUsers"]가 다른 곳과 철자/구조가 조금이라도 어긋나면 무효화가 조용히 실패합니다. 이 훅/관련 훅들에서 쿼리키를 상수(또는 queryKeys 유틸)로 통일하는 걸 권장합니다.

제안 diff
 export function useAcceptFriendRequest() {
   const qc = useQueryClient();

+  const receivedRequestsKey = ["friendRequests", "received"] as const;
+  const friendsKey = ["friends"] as const;
+  const searchUsersKey = ["searchUsers"] as const;
+
   return useMutation<boolean, ApiError, AcceptFriendRequestParams>({
     mutationKey: ["friendRequests", "accept"],
     mutationFn: (params) => acceptFriendRequest(params),
     onSuccess: () => {
-      qc.invalidateQueries({ queryKey: ["friendRequests", "received"] });
-      qc.invalidateQueries({ queryKey: ["friends"] });
-      qc.invalidateQueries({ queryKey: ["searchUsers"] });
+      qc.invalidateQueries({ queryKey: receivedRequestsKey });
+      qc.invalidateQueries({ queryKey: friendsKey });
+      qc.invalidateQueries({ queryKey: searchUsersKey });
     },
   });
 }
src/features/friend/requests/ui/AcceptFriendRequestButton.tsx (2)

3-9: UI props를 요청 DTO 타입과 분리하는 게 안전합니다

AcceptFriendRequestParams는 “API 요청 파라미터” 성격이라 UI props로 직접 쓰면 결합도가 커집니다(필드 추가/변경 시 UI가 연쇄 수정). Pick 또는 별도 Props 타입으로 분리 권장합니다.

제안 diff
 import { useAcceptFriendRequest } from "../hooks/useAcceptFriendRequest";
-import { AcceptFriendRequestParams } from "../model/types";
+import type { AcceptFriendRequestParams } from "../model/types";
+
+type Props = Pick<AcceptFriendRequestParams, "requestId">;

 export default function AcceptFriendRequestButton({
   requestId,
-}: AcceptFriendRequestParams) {
+}: Props) {
   const { mutate, isPending, isError, error } = useAcceptFriendRequest();

11-23: 에러 메시지 컨테이너에 role="alert" 추가를 고려하세요

현재도 동작은 하지만, 실패 메시지를 즉시 알리려면 role="alert" 정도는 붙이는 편이 낫습니다.

제안 diff
-      {isError && <div className="border">{error?.message}</div>}
+      {isError && (
+        <div className="border" role="alert">
+          {error?.message}
+        </div>
+      )}
src/features/friend/requests/ui/FriendRequestCard.tsx (1)

12-16: createdAt 원문 문자열 노출은 UX 이슈가 될 수 있어요

서버가 ISO 문자열을 내려주면 그대로 보이기 쉬워서, 최소한 로케일 포맷팅(또는 상대시간)을 적용하는 쪽을 권장합니다.

src/features/friend/requests/api/fetchRecivedFriendRequests.ts (1)

1-16: API 모듈에서 “DTO → Domain 정규화” 책임을 갖는 구조를 고려하세요

현재는 ReceivedFriendRequestsResponse를 그대로 반환하는데, UI가 응답 shape(예: createdAt: string)에 직접 의존하기 쉬워집니다. 가능하면 ...Dto 타입을 분리하고 이 계층에서 도메인 형태로 매핑하는 패턴을 추천합니다(가이드라인 기준).
추가로 파일명 fetchRecivedFriendRequests.tsReceived로 통일하는 게 좋습니다(향후 검색/자동완성/쿼리키 문자열과 함께 오타 전파 위험).

src/features/friend/requests/ui/FriendsReceivedRequestList.tsx (1)

3-4: Recived/컴포넌트 명칭 오타로 유지보수 비용 증가 (Received로 통일 권장)

  • Line 3, Line 17: useFetchRecivedFriendRequest, recived 등 오타가 타입/훅/쿼리키까지 전파된 상태로 보여서, 이후 검색/리팩터링/온보딩에 계속 비용이 생깁니다.
  • Line 6: 파일명은 FriendsReceivedRequestList.tsx인데 컴포넌트는 FriendsRequestList라서 import 시 혼란 여지가 큽니다.
제안 diff (이 파일 내에서 가능한 범위)
-import { useFetchRecivedFriendRequest } from "../hooks/useFetchRecivedFriendRequest";
+import { useFetchRecivedFriendRequest } from "../hooks/useFetchRecivedFriendRequest";
 import FriendRequestCard from "./FriendRequestCard";

-export default function FriendsRequestList() {
+export default function FriendsReceivedRequestList() {
   const { data, isLoading, isError } = useFetchRecivedFriendRequest({
     limit: 20,
   });

-  const recived = data?.items ?? [];
+  const received = data?.items ?? [];
...
-      {recived.length === 0 ? (
+      {received.length === 0 ? (
...
-          {recived.map((item) => (
+          {received.map((item) => (
             <FriendRequestCard key={item.requestId} item={item} />
           ))}

Also applies to: 6-7, 17-17

src/features/friend/requests/ui/SendFriendRequestButton.tsx (2)

13-22: disabled 상태인데 cursor-pointer 고정 + 에러 메시지 무스타일 노출

  • Line 15-19: disabled인데도 포인터 커서가 유지됩니다(UX 미세 저하).
  • Line 22: error.message를 그대로 노출하면 서버 메시지(내부 사유)가 그대로 사용자에게 보일 수 있어, 사용자 친화 문구로 매핑하거나 최소한 스타일/role을 주는 편이 안전합니다.
제안 diff
       <button
         type="button"
-        className="border cursor-pointer"
+        className="border cursor-pointer disabled:cursor-not-allowed disabled:opacity-60"
         onClick={() => mutate({ targetSlug })}
         disabled={isPending}
       >
         {isPending ? "요청중..." : "친구추가"}
       </button>

-      {isError && <div>{error?.message}</div>}
+      {isError && (
+        <div className="text-sm text-red-600" role="alert">
+          요청에 실패했습니다.
+        </div>
+      )}

6-23: 보낸 친구요청 목록 기능 추가 시 해당 쿼리키도 invalidate 필요

Line 14의 onSuccess는 현재 ["searchUsers"]만 invalidate하는데, 이는 검색 결과의 "요청중" 상태를 갱신하기 위해 적절합니다. 다만 코드의 18번 줄 주석에서 이미 언급했듯이, 향후 "보낸 친구요청 목록" 기능이 추가된다면 해당 쿼리키도 함께 invalidate해야 할 것으로 보입니다.

참고로 친구 목록(["friends"])은 친구 요청 수락(useAcceptFriendRequest) 또는 삭제(useRemoveFriend)할 때만 invalidate되므로, 요청 발송 시점에는 invalidate하지 않아도 됩니다.

src/features/user/search/ui/UserSearchCard.tsx (2)

3-4: 모듈 경계: user/search UI가 friend/requests UI에 직접 의존(결합도↑)
Line 3, Line 23: UserSearchCard가 “유저 검색 카드” 역할을 넘어 “친구요청 보내기 액션”까지 내장해서, user feature가 friend feature에 직접 결합됩니다.

  • 추천: UserSearchCard는 표시만 담당하고, 우측 액션은 상위(페이지/리스트)에서 주입(render prop/slot)하도록 바꾸면 책임이 더 명확해집니다. (코딩 가이드의 “모듈 경계 최우선” 기준)
제안 diff (slot 주입)
-import SendFriendRequestButton from "@/src/features/friend/requests/ui/SendFriendRequestButton";
 import type { SearchUser } from "../model/types";

-export default function UserSearchCard({ user }: { user: SearchUser }) {
+export default function UserSearchCard({
+  user,
+  right,
+}: {
+  user: SearchUser;
+  right?: React.ReactNode;
+}) {
   return (
     <div className="border p-3 text-black flex justify-between">
...
-      <div>
-        <SendFriendRequestButton targetSlug={user.slug} />
-      </div>
+      <div>{right}</div>
     </div>
   );
 }

Also applies to: 6-25


16-19: relationship 값이 내부 enum/raw 값으로 노출될 가능성
Line 16-19: {user.relationship} 그대로 출력하면 사용자에게 의미 없는 값(예: PENDING, NONE 등)이 보일 수 있어 매핑 테이블로 UI 문구를 분리하는 편이 안전합니다.

src/features/user/search/ui/UserSearchList.tsx (1)

11-18: 리스트 key는 slug보다 id가 더 안전 (충돌/변경 가능성)
Line 16: SearchUserid: number가 있는데도 user.slug를 key로 쓰고 있습니다. slug가 변경되거나 중복될 여지가 있으면 렌더링 안정성이 깨질 수 있어 user.id가 더 안전합니다.

제안 diff
       {items.map((user) => (
-        <UserSearchCard key={user.slug} user={user} />
+        <UserSearchCard key={user.id} user={user} />
       ))}
src/features/user/search/ui/UserSearchBox.tsx (2)

8-10: 타이핑마다 즉시 검색 호출 → 디바운스/Deferred 적용 권장
Line 10, Line 19: 현재는 입력 변경마다 쿼리가 바뀌어 호출될 가능성이 높습니다. 서버/쿼터/UX 측면에서 useDeferredValue 또는 디바운스(예: 200~300ms) 적용을 추천합니다.

제안 diff (useDeferredValue)
 import { useState } from "react";
+import { useDeferredValue } from "react";
 import { useSearchUsers } from "../hooks/useSearchUsers";
 import UserSearchList from "./UserSearchList";

 export default function UserSearchBox() {
   const [query, setQuery] = useState("");
+  const deferredQuery = useDeferredValue(query);

-  const { data, isLoading, isError } = useSearchUsers({ query });
+  const { data, isLoading, isError } = useSearchUsers({ query: deferredQuery });

Also applies to: 15-20


14-20: 입력 접근성: label/aria-label 없이 placeholder만 존재
Line 15-18: placeholder는 라벨을 대체하지 못하니(특히 스크린리더/자동완성), 최소 aria-label이라도 추가하는 게 안전합니다.

제안 diff
       <input
         className="border px-2 py-1 w-full"
         placeholder="유저 검색 (slug/닉네임)"
+        aria-label="유저 검색 (slug/닉네임)"
         value={query}
         onChange={(e) => setQuery(e.target.value)}
       />
src/features/friend/list/ui/FriendsList.tsx (1)

6-29: console.log 제거 + 리스트 key는 slug보다 id 우선 권장

  • Line 8의 console.log(data)는 프로덕션 번들/로그 노이즈라 제거하는 게 좋아요.
  • Line 26에서 key로 f.slug를 쓰는데, slug가 변경 가능하거나 유니크 보장이 약하면 리렌더/상태 꼬임이 생깁니다. Friend.id가 있으면 key={f.id}가 더 안전합니다.
Proposed fix
 export default function FriendsList() {
   const { data, isLoading, isError } = useFriendsList({ size: 20 });
-  console.log(data);

   if (isLoading)
     return <div className="border p-4 text-black">친구목록 로딩중...</div>;
   if (isError)
     return <div className="border p-4 text-black">친구목록 로딩 실패</div>;
@@
         <ul className="space-y-2">
           {friends.map((f) => (
-            <FriendCard key={f.slug} friend={f} />
+            <FriendCard key={f.id} friend={f} />
           ))}
         </ul>
       )}
     </div>
   );
 }
src/features/friend/list/hooks/useFriendsList.ts (1)

7-12: queryKey는 괜찮지만, params 확장 대비 “객체 key” 형태도 고려해볼 만해요

현재는 lastId/size만 키에 들어가서 문제는 적지만, 필드가 늘면 배열 key가 길어지고 실수하기 쉬워집니다. queryKey: ["friends", { lastId: ..., size: ... }]처럼 객체로 묶으면 유지보수성이 좋아집니다.

src/entities/friend/model/types.ts (1)

1-11: FriendsListResponse가 “도메인 타입”인지 “DTO”인지 경계가 애매합니다.

src/entities/friend/model/types.tsFriendsListResponse(items/hasNext)가 들어가면 UI/네트워크 응답 형태가 엔티티 레벨로 전파될 수 있어요. DTO라면 src/features/.../api(또는 src/entities/.../api) 쪽으로 두고 toDomain 매핑을 통해 Friend[] 같은 도메인 형태로만 노출하는 쪽을 권장합니다. (가이드라인의 **/api/**/*.ts* 책임 기준)

또한 Friend.id: string인데, 페이지네이션 커서가 lastId?: number로 설계돼 있으면 타입 충돌 여지가 큽니다(서버 스펙 확인 필요).

src/features/friend/list/api/fetchFriends.ts (1)

1-15: API 모듈에서 “raw 응답 타입”을 그대로 반환하지 않게 정규화(toDomain)하는 구조가 더 안전합니다.

현재 fetchFriends()ApiResponse<FriendsListResponse>result를 그대로 리턴해서, UI/훅이 네트워크 DTO 형태에 결합될 가능성이 큽니다. FriendsListResponseFriendsListDto로 두고, 여기서 toDomain 변환(예: Friend[] + hasNext)까지 책임지는 형태를 권장합니다. (코딩 가이드라인 **/api/**/*.ts* 기준)

추가로 FetchFriendsParams.lastId?: numberFriend.id: string의 커서 타입 불일치 가능성은 실제 런타임 버그(페이지네이션 끊김)로 이어질 수 있으니 서버 스펙과 반드시 맞춰주세요.

src/app/test/page.tsx (2)

21-45: 2단 레이아웃 반응형 처리 + 불필요한 래퍼 제거를 권장합니다

  • 현재 flex gap-4 2단 고정이라 모바일에서 가독성이 급격히 떨어질 수 있어 flex-col md:flex-row 같은 반응형 분기가 있으면 좋겠습니다.
  • FriendsRequestList를 감싸는 <div>(Line 38-40)는 역할이 없어서 제거해도 됩니다.
  • (유지보수 관점) “닉네임/검색”, “방/친구”처럼 섹션 경계가 명확하니 <section> + 제목 컴포넌트 같은 얕은 구조화도 고려할 만합니다.

예시(클래스만):

-      <div className="flex gap-4">
+      <div className="flex flex-col md:flex-row gap-4">
...
-      <div className="flex gap-4">
+      <div className="flex flex-col md:flex-row gap-4">
...
-          <div>
-            <FriendsRequestList />
-          </div>
+          <FriendsRequestList />

5-12: FriendsRequestList 파일/컴포넌트 네이밍 정합성만 확인해 주세요

FriendsRequestListFriendsReceivedRequestList 파일에서 가져오고 있어(임포트 경로 기준) “받은 요청/보낸 요청/전체 요청”이 늘어날 때 혼동될 여지가 있습니다.
가능하면 파일명/컴포넌트명/표시 텍스트가 같은 축(Received/Sent/Requests)으로 정렬되도록 정리해 두는 걸 권장합니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1aba936 and e35dbb0.

📒 Files selected for processing (40)
  • src/app/test/page.tsx
  • src/entities/friend/model/types.ts
  • src/entities/room/api/createRoom.ts
  • src/entities/room/api/deleteRoom.ts
  • src/entities/room/hooks/useDeleteRoom.ts
  • src/entities/room/ui/RoomCard.tsx
  • src/features/friend/list/api/fetchFriends.ts
  • src/features/friend/list/hooks/useFriendsList.ts
  • src/features/friend/list/model/types.ts
  • src/features/friend/list/ui/FriendCard.tsx
  • src/features/friend/list/ui/FriendsList.tsx
  • src/features/friend/remove/api/removeFriend.ts
  • src/features/friend/remove/hooks/useRemoveFriend.ts
  • src/features/friend/remove/model/types.ts
  • src/features/friend/remove/ui/RemoveFriendButton.tsx
  • src/features/friend/requests/api/acceptFriendRequest.ts
  • src/features/friend/requests/api/fetchRecivedFriendRequests.ts
  • src/features/friend/requests/api/sendFriendRequest.ts
  • src/features/friend/requests/hooks/useAcceptFriendRequest.ts
  • src/features/friend/requests/hooks/useFetchRecivedFriendRequest.ts
  • src/features/friend/requests/hooks/useSendFriendRequest.ts
  • src/features/friend/requests/model/types.ts
  • src/features/friend/requests/ui/AcceptFriendRequestButton.tsx
  • src/features/friend/requests/ui/FriendRequestCard.tsx
  • src/features/friend/requests/ui/FriendsReceivedRequestList.tsx
  • src/features/friend/requests/ui/SendFriendRequestButton.tsx
  • src/features/room/create/ui/CreateRoomTest.tsx
  • src/features/room/delete/ui/DeleteRoomButton.tsx
  • src/features/room/list/ui/RoomListTest.tsx
  • src/features/user/profile/api/updateMe.ts
  • src/features/user/profile/hooks/useUpdateMe.ts
  • src/features/user/profile/model/types.ts
  • src/features/user/profile/ui/CheckNicknameButton.tsx
  • src/features/user/profile/ui/NicknameEditForm.tsx
  • src/features/user/search/api/searchUsers.ts
  • src/features/user/search/hooks/useSearchUsers.ts
  • src/features/user/search/model/types.ts
  • src/features/user/search/ui/UserSearchBox.tsx
  • src/features/user/search/ui/UserSearchCard.tsx
  • src/features/user/search/ui/UserSearchList.tsx
🧰 Additional context used
📓 Path-based instructions (4)
**/*

⚙️ CodeRabbit configuration file

**/*: - 리뷰는 한국어로 작성.

  • 유지보수/확장성 관점에서 모듈 경계(의존성 방향, 책임 분리)가 적절한지 최우선으로 확인.
  • 파일/함수 책임이 과도하면 응집도/결합도 기준으로 분리/통합 개선안을 제시.
  • TypeScript에서 any/과도한 as 캐스팅을 지양하고, 도메인 타입(예: ProfileId 같은 ID 타입)으로 의미를 드러내는지 확인.
  • React에서 상태 위치 미스, 불필요한 리렌더링, derived state 남발, useEffect 남용을 지적하고 개선안을 제시.

Files:

  • src/features/friend/remove/model/types.ts
  • src/entities/friend/model/types.ts
  • src/entities/room/api/deleteRoom.ts
  • src/features/user/search/api/searchUsers.ts
  • src/features/friend/requests/hooks/useSendFriendRequest.ts
  • src/features/friend/remove/api/removeFriend.ts
  • src/features/friend/requests/ui/AcceptFriendRequestButton.tsx
  • src/features/user/search/ui/UserSearchCard.tsx
  • src/features/user/search/ui/UserSearchBox.tsx
  • src/features/friend/list/hooks/useFriendsList.ts
  • src/features/friend/requests/api/sendFriendRequest.ts
  • src/features/friend/requests/api/fetchRecivedFriendRequests.ts
  • src/features/friend/list/api/fetchFriends.ts
  • src/entities/room/hooks/useDeleteRoom.ts
  • src/features/friend/list/ui/FriendCard.tsx
  • src/features/friend/list/ui/FriendsList.tsx
  • src/features/friend/requests/hooks/useAcceptFriendRequest.ts
  • src/features/friend/requests/ui/FriendRequestCard.tsx
  • src/features/friend/requests/hooks/useFetchRecivedFriendRequest.ts
  • src/features/friend/requests/model/types.ts
  • src/entities/room/ui/RoomCard.tsx
  • src/features/friend/remove/ui/RemoveFriendButton.tsx
  • src/features/friend/remove/hooks/useRemoveFriend.ts
  • src/features/user/search/model/types.ts
  • src/features/friend/requests/ui/FriendsReceivedRequestList.tsx
  • src/features/friend/requests/api/acceptFriendRequest.ts
  • src/features/friend/list/model/types.ts
  • src/features/user/search/hooks/useSearchUsers.ts
  • src/features/room/create/ui/CreateRoomTest.tsx
  • src/features/friend/requests/ui/SendFriendRequestButton.tsx
  • src/features/room/delete/ui/DeleteRoomButton.tsx
  • src/features/user/search/ui/UserSearchList.tsx
  • src/features/user/profile/ui/NicknameEditForm.tsx
  • src/app/test/page.tsx
  • src/entities/room/api/createRoom.ts
  • src/features/room/list/ui/RoomListTest.tsx
**/api/**/*.ts*

⚙️ CodeRabbit configuration file

**/api/**/*.ts*: - API 모듈은 DTO 타입 + 네트워크 호출 + toDomain 매핑까지 책임지는 구조를 우선 권장.

  • UI에서 raw DTO(res.json())를 직접 다루지 않도록, 반환 타입은 Domain(User 등)으로 정규화.
  • fetch 옵션(credentials, headers 등) 규약이 일관적인지 확인.

Files:

  • src/entities/room/api/deleteRoom.ts
  • src/features/user/search/api/searchUsers.ts
  • src/features/friend/remove/api/removeFriend.ts
  • src/features/friend/requests/api/sendFriendRequest.ts
  • src/features/friend/requests/api/fetchRecivedFriendRequests.ts
  • src/features/friend/list/api/fetchFriends.ts
  • src/features/friend/requests/api/acceptFriendRequest.ts
  • src/entities/room/api/createRoom.ts
**/ui/**/*.tsx

⚙️ CodeRabbit configuration file

**/ui/**/*.tsx: - 상태 위치가 적절한지(상/하위 이동 필요성) 지적하고 개선안 제시.

  • 파생 값은 state로 두지 말고 계산(useMemo는 필요할 때만).
  • 리스트 key 안정성, 불필요한 re-render 유발 props(익명 함수/객체) 지적.

Files:

  • src/features/friend/requests/ui/AcceptFriendRequestButton.tsx
  • src/features/user/search/ui/UserSearchCard.tsx
  • src/features/user/search/ui/UserSearchBox.tsx
  • src/features/friend/list/ui/FriendCard.tsx
  • src/features/friend/list/ui/FriendsList.tsx
  • src/features/friend/requests/ui/FriendRequestCard.tsx
  • src/entities/room/ui/RoomCard.tsx
  • src/features/friend/remove/ui/RemoveFriendButton.tsx
  • src/features/friend/requests/ui/FriendsReceivedRequestList.tsx
  • src/features/room/create/ui/CreateRoomTest.tsx
  • src/features/friend/requests/ui/SendFriendRequestButton.tsx
  • src/features/room/delete/ui/DeleteRoomButton.tsx
  • src/features/user/search/ui/UserSearchList.tsx
  • src/features/user/profile/ui/NicknameEditForm.tsx
  • src/features/room/list/ui/RoomListTest.tsx
src/app/**/*.ts*

⚙️ CodeRabbit configuration file

src/app/**/*.ts*: - Next.js App Router 규칙 준수: server/client 컴포넌트 경계가 적절한지 확인.

  • 가능하면 데이터 패칭은 async 서버 컴포넌트/route handler에서 처리하고, 클라에서 불필요한 fetch/useEffect를 줄일 것.
  • "use client"는 꼭 필요한 곳에만.

Files:

  • src/app/test/page.tsx
🧬 Code graph analysis (29)
src/entities/room/api/deleteRoom.ts (2)
src/shared/api/axiosInstance.ts (1)
  • axiosInstance (5-12)
src/shared/api/types.ts (1)
  • ApiResponse (1-1)
src/features/user/search/api/searchUsers.ts (3)
src/features/user/search/model/types.ts (2)
  • SearchUserParams (15-19)
  • SearchUsersResponse (10-13)
src/shared/api/axiosInstance.ts (1)
  • axiosInstance (5-12)
src/shared/api/types.ts (1)
  • ApiResponse (1-1)
src/features/friend/requests/hooks/useSendFriendRequest.ts (3)
src/shared/api/api-error.ts (1)
  • ApiError (1-11)
src/features/friend/requests/model/types.ts (1)
  • SendFriendRequestPayload (1-3)
src/features/friend/requests/api/sendFriendRequest.ts (1)
  • sendFriendRequest (6-14)
src/features/friend/remove/api/removeFriend.ts (3)
src/features/friend/remove/model/types.ts (1)
  • RemoveFriendParams (1-3)
src/shared/api/axiosInstance.ts (1)
  • axiosInstance (5-12)
src/shared/api/types.ts (1)
  • ApiResponse (1-1)
src/features/friend/requests/ui/AcceptFriendRequestButton.tsx (2)
src/features/friend/requests/model/types.ts (1)
  • AcceptFriendRequestParams (24-26)
src/features/friend/requests/hooks/useAcceptFriendRequest.ts (1)
  • useAcceptFriendRequest (8-20)
src/features/user/search/ui/UserSearchCard.tsx (2)
src/features/user/search/model/types.ts (1)
  • SearchUser (5-8)
src/features/friend/requests/ui/SendFriendRequestButton.tsx (1)
  • SendFriendRequestButton (6-25)
src/features/user/search/ui/UserSearchBox.tsx (2)
src/features/user/search/hooks/useSearchUsers.ts (1)
  • useSearchUsers (6-13)
src/features/user/search/ui/UserSearchList.tsx (1)
  • UserSearchList (6-20)
src/features/friend/list/hooks/useFriendsList.ts (4)
src/features/friend/list/model/types.ts (1)
  • FetchFriendsParams (1-4)
src/entities/friend/model/types.ts (1)
  • FriendsListResponse (8-11)
src/shared/api/api-error.ts (1)
  • ApiError (1-11)
src/features/friend/list/api/fetchFriends.ts (1)
  • fetchFriends (6-15)
src/features/friend/requests/api/sendFriendRequest.ts (3)
src/features/friend/requests/model/types.ts (1)
  • SendFriendRequestPayload (1-3)
src/shared/api/axiosInstance.ts (1)
  • axiosInstance (5-12)
src/shared/api/types.ts (1)
  • ApiResponse (1-1)
src/features/friend/requests/api/fetchRecivedFriendRequests.ts (3)
src/features/friend/requests/model/types.ts (2)
  • FetchReceivedFriendRequestsParams (19-22)
  • ReceivedFriendRequestsResponse (14-17)
src/shared/api/axiosInstance.ts (1)
  • axiosInstance (5-12)
src/shared/api/types.ts (1)
  • ApiResponse (1-1)
src/features/friend/list/api/fetchFriends.ts (4)
src/features/friend/list/model/types.ts (1)
  • FetchFriendsParams (1-4)
src/entities/friend/model/types.ts (1)
  • FriendsListResponse (8-11)
src/shared/api/axiosInstance.ts (1)
  • axiosInstance (5-12)
src/shared/api/types.ts (1)
  • ApiResponse (1-1)
src/entities/room/hooks/useDeleteRoom.ts (1)
src/entities/room/api/deleteRoom.ts (1)
  • deleteRoom (4-9)
src/features/friend/list/ui/FriendCard.tsx (2)
src/entities/friend/model/types.ts (1)
  • Friend (1-6)
src/features/friend/remove/ui/RemoveFriendButton.tsx (1)
  • RemoveFriendButton (6-19)
src/features/friend/list/ui/FriendsList.tsx (2)
src/features/friend/list/hooks/useFriendsList.ts (1)
  • useFriendsList (7-13)
src/features/friend/list/ui/FriendCard.tsx (1)
  • FriendCard (7-19)
src/features/friend/requests/hooks/useAcceptFriendRequest.ts (3)
src/shared/api/api-error.ts (1)
  • ApiError (1-11)
src/features/friend/requests/model/types.ts (1)
  • AcceptFriendRequestParams (24-26)
src/features/friend/requests/api/acceptFriendRequest.ts (1)
  • acceptFriendRequest (5-13)
src/features/friend/requests/ui/FriendRequestCard.tsx (2)
src/features/friend/requests/model/types.ts (1)
  • ReceivedFriendRequest (5-12)
src/features/friend/requests/ui/AcceptFriendRequestButton.tsx (1)
  • AcceptFriendRequestButton (6-25)
src/features/friend/requests/hooks/useFetchRecivedFriendRequest.ts (3)
src/features/friend/requests/model/types.ts (2)
  • FetchReceivedFriendRequestsParams (19-22)
  • ReceivedFriendRequestsResponse (14-17)
src/shared/api/api-error.ts (1)
  • ApiError (1-11)
src/features/friend/requests/api/fetchRecivedFriendRequests.ts (1)
  • fetchReceivedFriendRequests (8-16)
src/entities/room/ui/RoomCard.tsx (1)
src/entities/room/model/types.ts (1)
  • RoomTag (1-4)
src/features/friend/remove/ui/RemoveFriendButton.tsx (2)
src/features/friend/remove/model/types.ts (1)
  • RemoveFriendParams (1-3)
src/features/friend/remove/hooks/useRemoveFriend.ts (1)
  • useRemoveFriend (8-19)
src/features/friend/remove/hooks/useRemoveFriend.ts (3)
src/shared/api/api-error.ts (1)
  • ApiError (1-11)
src/features/friend/remove/model/types.ts (1)
  • RemoveFriendParams (1-3)
src/features/friend/remove/api/removeFriend.ts (1)
  • removeFriend (5-11)
src/features/user/search/model/types.ts (1)
src/entities/user/model/types.ts (1)
  • User (1-5)
src/features/friend/requests/ui/FriendsReceivedRequestList.tsx (2)
src/features/friend/requests/hooks/useFetchRecivedFriendRequest.ts (1)
  • useFetchRecivedFriendRequest (11-23)
src/features/friend/requests/ui/FriendRequestCard.tsx (1)
  • FriendRequestCard (6-23)
src/features/friend/requests/api/acceptFriendRequest.ts (3)
src/features/friend/requests/model/types.ts (1)
  • AcceptFriendRequestParams (24-26)
src/shared/api/axiosInstance.ts (1)
  • axiosInstance (5-12)
src/shared/api/types.ts (1)
  • ApiResponse (1-1)
src/features/room/create/ui/CreateRoomTest.tsx (2)
src/features/room/create/model/useCreateRoom.ts (1)
  • useCreateRoom (12-23)
src/entities/room/hooks/useRoomTags.ts (1)
  • useRoomTags (4-11)
src/features/friend/requests/ui/SendFriendRequestButton.tsx (2)
src/features/friend/requests/model/types.ts (1)
  • SendFriendRequestPayload (1-3)
src/features/friend/requests/hooks/useSendFriendRequest.ts (1)
  • useSendFriendRequest (8-16)
src/features/room/delete/ui/DeleteRoomButton.tsx (1)
src/entities/room/hooks/useDeleteRoom.ts (1)
  • useDeleteRoom (4-14)
src/app/test/page.tsx (7)
src/features/user/profile/ui/NicknameEditForm.tsx (1)
  • NicknameEditForm (7-57)
src/features/user/search/ui/UserSearchBox.tsx (1)
  • UserSearchBox (7-28)
src/entities/user/ui/IsLogin.tsx (1)
  • IsLogin (3-21)
src/features/room/create/ui/CreateRoomTest.tsx (1)
  • CreateRoomTest (9-135)
src/features/room/list/ui/RoomListTest.tsx (1)
  • RoomsListTest (8-50)
src/features/friend/requests/ui/FriendsReceivedRequestList.tsx (1)
  • FriendsRequestList (6-34)
src/features/friend/list/ui/FriendsList.tsx (1)
  • FriendsList (6-32)
src/entities/room/api/createRoom.ts (3)
src/shared/api/types.ts (1)
  • ApiResponse (1-1)
src/entities/room/api/types.ts (2)
  • CreateRoomPayload (1-5)
  • CreateRoomResult (7-9)
src/shared/api/axiosInstance.ts (1)
  • axiosInstance (5-12)
src/features/room/list/ui/RoomListTest.tsx (3)
src/entities/room/ui/RoomCard.tsx (1)
  • RoomCard (10-33)
src/features/room/join/JoinRoomButton.tsx (1)
  • JoinRoomButton (9-20)
src/features/room/delete/ui/DeleteRoomButton.tsx (1)
  • DeleteRoomButton (9-22)
🔇 Additional comments (5)
src/features/user/profile/ui/NicknameEditForm.tsx (1)

5-5: 디렉토리 이동에 따른 import 경로 정리 적절합니다.
user-profile → user/profile 정리 흐름에 맞게 의존 경로가 일관되게 맞춰졌습니다.

src/features/user/search/api/searchUsers.ts (1)

1-13: 코드는 기존 코드베이스 패턴과 일치하므로 수정 불필요

리뷰 가이드라인의 "DTO + 네트워크 호출 + toDomain 매핑" 권장과 달리, 현재 코드베이스의 모든 API 모듈이 동일한 패턴을 따릅니다: ApiResponse<T> 래핑 후 res.data.result 직접 반환. SearchUsersResponse는 이미 User 도메인 엔티티 기반으로 정규화되어 있으며, 엔드포인트(/api/v1/user-profiles)와 params 처리(query 필수, lastId/limit 선택)도 일관성 있게 구성되어 있습니다. 추가 매핑은 불필요합니다.

src/features/friend/list/ui/FriendsList.tsx (1)

7-13: isLoading 사용이 react-query 버전에 따라 깨질 수 있어요(특히 v5)

@tanstack/react-query v5에서는 isPending 중심으로 바뀐 케이스가 많아서, 현재 프로젝트 버전에 맞게 isLoading/isPending 플래그를 확인하는 게 안전합니다.

src/features/friend/list/hooks/useFriendsList.ts (1)

1-13: react-query 제네릭/옵션 형태가 현재 설치 버전과 정확히 맞는지 확인 필요

useQuery<FriendsListResponse, ApiError>({...}) 및 상태 플래그 네이밍은 버전에 따라 차이가 나서(특히 v4↔v5), 프로젝트 의존성 버전 기준으로 타입 에러/런타임 동작을 한번 확인해 주세요.

src/entities/room/hooks/useDeleteRoom.ts (1)

1-14: 쿼리 키 정합성만 확인되면 전반적으로 깔끔합니다.

onSuccess에서 ["rooms"]를 invalidate 하는 방식이 useRoomsQuery의 실제 queryKey(예: ["rooms"] vs ["rooms", filters])와 의도대로 매칭되는지만 확인해주세요. 가능하면 mutationFn: deleteRoom로도 간단히 줄일 수 있어요.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment thread src/entities/room/api/deleteRoom.ts
Comment thread src/entities/room/ui/RoomCard.tsx Outdated
Comment thread src/features/friend/list/model/types.ts
Comment thread src/features/friend/list/ui/FriendCard.tsx Outdated
Comment thread src/features/friend/remove/api/removeFriend.ts Outdated
Comment thread src/features/friend/requests/api/sendFriendRequest.ts
Comment thread src/features/friend/requests/hooks/useFetchReceivedFriendRequest.ts
Comment thread src/features/friend/requests/hooks/useFetchReceivedFriendRequest.ts
Comment thread src/features/friend/requests/ui/FriendsReceivedRequestList.tsx
Comment thread src/features/room/delete/ui/DeleteRoomButton.tsx
@aryu1217 aryu1217 changed the title Feat/friend Feat/친구 추가, 삭제, 요청 목록 조회 기능 추가 Jan 14, 2026
@aryu1217 aryu1217 merged commit 2c88472 into main Jan 15, 2026
1 check 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