Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. WalkthroughRoom 타입에 Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor U as User
participant M as MyGroupModal
participant API as getMyRooms(roomType, cursor)
participant Map as convertRoomToGroup
rect rgb(245,248,255)
note over M: 모달 열림 시 초기화 및 첫 로드
U->>M: 모달 열기 / 탭 전환
M->>API: fetch(roomType, cursor=null)
API-->>M: { rooms[], nextCursor, isLast }
M->>Map: rooms.map(convertRoomToGroup)
Map-->>M: groups(with isPublic)
M-->>U: 그룹 목록 렌더 + 스크롤 맨 위
end
rect rgb(240,255,245)
note over M: 무한 스크롤 로드
U->>M: 목록 스크롤 하단 근접
M->>M: isFetching / isLast 검사
M->>API: fetch(roomType, nextCursor)
API-->>M: { rooms[], nextCursor, isLast }
M->>Map: rooms.map(convertRoomToGroup)
Map-->>M: more groups
M-->>U: 목록에 추가, 스피너 숨김
end
rect rgb(255,245,245)
note over M: 오류 처리
API-->>M: error
M-->>U: 에러 메시지 표시
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. 📜 Recent review detailsConfiguration used: CodeRabbit UI 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (1)
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (12)
src/pages/searchBook/SearchBook.styled.ts (1)
118-132: 하드코딩된 44px을 토큰으로 승격 검토여러 컴포넌트에서 동일 값(44px)을 반복 사용하니, 추후 테마 확장성을 위해 spacing/size 토큰으로 승격하는 것을 권장합니다.
Also applies to: 141-157, 159-169
src/components/search/MostSearchedBooks.tsx (1)
148-149: max-width의 매직 넘버(77px) 제거 제안77px(= Rank 24 + Cover 45 + margin-left 8)의 하드코딩은 유지보수에 취약합니다. flex: 1, min-width: 0만으로도 충분히 생략 처리가 가능하므로 max-width를 제거하는 것을 권장합니다.
아래처럼 단순화해도 동작은 동일합니다:
- flex: 1 1 0%; - min-width: 0; - max-width: calc(100% - 77px); + flex: 1 1 0%; + min-width: 0;src/components/group/GroupCard.tsx (1)
15-16: GroupCard 컴포넌트: 사용되지 않는 isPublic prop 처리 필요현재
src/components/group/GroupCard.tsx에서
- 15열:
isPublic?: boolean;prop이 선언만 되어 있고- 24열:
{group.isPublic === false && (…)}처럼 항상group.isPublic만 참조되고, 호출부에서도isPublicprop을 전달하지 않습니다.다음 중 하나로 정리해 주세요:
- 호출부에서
isPublicprop을 전달하지 않는다면, 15열의 prop 선언을 제거- 호출부에서 전달 중이라면, 컴포넌트에서 구조분해 할당으로 prop을 받아 사용하도록 수정
예시)interface Props { group: Group; isPublic?: boolean; } function GroupCard({ group, isPublic = group.isPublic }: Props) { return ( … {isPublic === false && (…)} … ); }src/components/feed/BookInfoCard.tsx (2)
25-25: 우측 화살표 아이콘 접근성 보완 제안장식용 아이콘이면 스크린리더에서 숨기는 것이 좋습니다. alt=""와 aria-hidden="true"를 추가해 주세요.
- <img src={rightArrow} /> + <img src={rightArrow} alt="" aria-hidden="true" />
31-43: 클릭 가능한 영역의 접근성(키보드 내비게이션) 개선 제안BookContainer가 div+onClick으로 구현되어 키보드 포커스/Enter 동작이 기본적으로 지원되지 않습니다. a 또는 button으로 바꾸거나 role="button", tabIndex=0, onKeyDown(Enter/Space)을 추가하는 방안을 고려해 주세요.
src/components/group/MyGroupBox.tsx (2)
22-22: Group 타입 확장(isPublic) 반영 확인타입 확장은 합리적입니다. 다만 convertJoinedRoomToGroup에서 isPublic을 아직 매핑하지 않습니다(옵셔널이라 런타임 이슈는 없음). JoinedRoomItem에 isPublic이 존재한다면 함께 매핑해 두면 이후 GroupCard 재사용 시(락 오버레이 표기) 혼선을 줄일 수 있습니다.
73-92: 드래그 상태 관리 useRef로 전환하여 재렌더 안전성 확보현재 let 변수는 재렌더 시 초기화될 수 있어 드래그 중 외부 요인으로 재렌더가 발생하면 끊길 수 있습니다. useRef로 상태를 관리하는 것이 안전합니다.
- let isDragging = false; - let startX = 0; - let scrollLeft = 0; + const isDraggingRef = useRef(false); + const startXRef = useRef(0); + const scrollLeftRef = useRef(0); const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => { - isDragging = true; - startX = e.pageX - (scrollRef.current?.offsetLeft ?? 0); - scrollLeft = scrollRef.current?.scrollLeft ?? 0; + isDraggingRef.current = true; + startXRef.current = e.pageX - (scrollRef.current?.offsetLeft ?? 0); + scrollLeftRef.current = scrollRef.current?.scrollLeft ?? 0; document.body.style.userSelect = 'none'; }; const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => { - if (!isDragging || !scrollRef.current) return; + if (!isDraggingRef.current || !scrollRef.current) return; const x = e.pageX - scrollRef.current.offsetLeft; - const walk = x - startX; - scrollRef.current.scrollLeft = scrollLeft - walk; + const walk = x - startXRef.current; + scrollRef.current.scrollLeft = scrollLeftRef.current - walk; }; const handleMouseUp = () => { - isDragging = false; + isDraggingRef.current = false; document.body.style.userSelect = ''; }; - let touchStartX = 0; - let touchScrollLeft = 0; + const touchStartXRef = useRef(0); + const touchScrollLeftRef = useRef(0); const handleTouchStart = (e: React.TouchEvent<HTMLDivElement>) => { - isDragging = true; - touchStartX = e.touches[0].pageX - (scrollRef.current?.offsetLeft ?? 0); - touchScrollLeft = scrollRef.current?.scrollLeft ?? 0; + isDraggingRef.current = true; + touchStartXRef.current = e.touches[0].pageX - (scrollRef.current?.offsetLeft ?? 0); + touchScrollLeftRef.current = scrollRef.current?.scrollLeft ?? 0; }; const handleTouchMove = (e: React.TouchEvent<HTMLDivElement>) => { - if (!isDragging || !scrollRef.current) return; + if (!isDraggingRef.current || !scrollRef.current) return; const x = e.touches[0].pageX - scrollRef.current.offsetLeft; - const walk = x - touchStartX; - scrollRef.current.scrollLeft = touchScrollLeft - walk; + const walk = x - touchStartXRef.current; + scrollRef.current.scrollLeft = touchScrollLeftRef.current - walk; }; const handleTouchEnd = () => { - isDragging = false; + isDraggingRef.current = false; };추가로, 다음 import 보완이 필요합니다(파일 상단):
import { useState, useEffect, useRef } from 'react';Also applies to: 94-109
src/components/group/MyGroupCard.tsx (1)
31-33: 진행도 바 폭 클램프 제안(0~100%)예외 데이터(음수/100% 초과)가 들어오면 진행도 바가 깨질 수 있습니다. 뷰 안전성을 위해 폭을 0~100%로 클램프 해주세요.
- <Fill width={group.progress || 0} /> + <Fill width={Math.max(0, Math.min(100, group.progress ?? 0))} />src/components/group/MyGroupModal.tsx (4)
159-170: 프리로드 guard 값/주석 불일치(오프바이원) 수정주석은 최대 3페이지인데 guard는 2로 설정되어 있습니다. 의도대로라면 3으로 맞추세요(또는 주석을 2로).
- let guard = 2; // 최대 3페이지까지 자동 프리로드(필요시 늘리기) + let guard = 3; // 최대 3페이지까지 자동 프리로드(필요시 늘리기)
17-22: 바디 스크롤 락 해제 시 기존 상태 보존기존 body overflow 값을 저장한 뒤 복원하면 다른 오버레이/모달과 충돌을 줄일 수 있습니다.
- useEffect(() => { - document.body.style.overflow = 'hidden'; - return () => { - document.body.style.overflow = ''; - }; - }, []); + useEffect(() => { + const prevOverflow = document.body.style.overflow; + document.body.style.overflow = 'hidden'; + return () => { + document.body.style.overflow = prevOverflow; + }; + }, []);
82-82: isFetchingRef 타입 명시타입 추론이 any로 갈 수 있으니 boolean 제네릭을 명시해 주세요.
- const isFetchingRef = useRef(false); + const isFetchingRef = useRef<boolean>(false);
298-305: 에러 색상도 디자인 토큰으로 통일토큰(colors.red)을 사용해 스타일 일관성을 유지하세요.
- color: #ff6b6b; + color: ${colors.red};
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
src/assets/common/filledSaveIcon.svgis excluded by!**/*.svg
📒 Files selected for processing (11)
src/api/rooms/getMyRooms.ts(1 hunks)src/components/common/Filter.tsx(0 hunks)src/components/feed/BookInfoCard.tsx(3 hunks)src/components/group/GroupCard.tsx(2 hunks)src/components/group/MyGroupBox.tsx(1 hunks)src/components/group/MyGroupCard.tsx(3 hunks)src/components/group/MyGroupModal.tsx(8 hunks)src/components/group/RecruitingGroupBox.tsx(2 hunks)src/components/search/MostSearchedBooks.tsx(1 hunks)src/pages/groupDetail/GroupDetail.styled.ts(1 hunks)src/pages/searchBook/SearchBook.styled.ts(5 hunks)
💤 Files with no reviewable changes (1)
- src/components/common/Filter.tsx
🧰 Additional context used
🧬 Code Graph Analysis (5)
src/components/group/RecruitingGroupBox.tsx (1)
src/styles/global/global.ts (1)
typography(56-77)
src/pages/groupDetail/GroupDetail.styled.ts (1)
src/styles/global/global.ts (2)
colors(4-53)typography(56-77)
src/components/group/MyGroupCard.tsx (2)
src/styles/global/global.ts (2)
typography(56-77)colors(4-53)src/components/group/RecordSection.styled.ts (1)
ProgressText(71-75)
src/components/feed/BookInfoCard.tsx (1)
src/styles/global/global.ts (2)
colors(4-53)typography(56-77)
src/components/group/MyGroupModal.tsx (3)
src/api/rooms/getMyRooms.ts (3)
Room(6-15)RoomType(3-3)getMyRooms(29-45)src/components/group/GroupCard.tsx (1)
GroupCard(18-52)src/styles/global/global.ts (2)
colors(4-53)typography(56-77)
🔇 Additional comments (13)
src/pages/searchBook/SearchBook.styled.ts (2)
118-132: 44px 터치 타깃 일관화 좋아요버튼/아이콘 버튼 높이/사이즈(44px) 일괄 조정으로 모바일 접근성 기준을 만족합니다.
Also applies to: 141-157, 159-169
171-177: FeedSection width 100% 변경에 따른 좌우 여백/오버플로우 확인 요청width를 96%→100%로 확대한 만큼, 내부 자식(특히 FilterContainer의 margin 0 20px, FeedTitle의 padding 20px)과 합쳐졌을 때 작은 해상도에서 수평 스크롤이 생기지 않는지 한 번 더 눈으로 확인 부탁드립니다.
Also applies to: 178-183
src/api/rooms/getMyRooms.ts (2)
14-15: API 스키마 반영 잘되었습니다도메인 상 비공개 방 상태를 클라이언트에서 명확히 구분할 수 있게 되어, UI 상태(오버레이 등) 연동이 수월해졌습니다.
14-15: Room APIisPublic필드 필수 여부 확인 필요
src/api/rooms/getMyRooms.ts(14–15)를 포함해 모든 room 관련 API 타입에서isPublic: boolean으로 선언되어 있고, 다수 컴포넌트가 이를 소비하고 있습니다.
서버 응답에 항상isPublic이 포함되는지 API 문서 혹은 백엔드 팀에 확인해 주세요.만약 일부 엔드포인트에서 누락될 가능성이 있다면, 다음 중 적절한 방안을 적용해 주세요:
- 프론트엔드 매핑 시 기본값(e.g.
true) 보강- 타입 정의를
isPublic?: boolean로 optional 처리??연산자 등으로 nullish fallback 처리src/components/search/MostSearchedBooks.tsx (1)
146-149: flex + min-width: 0 추가는 텍스트 생략 처리에 적합합니다플렉스 컨테이너에서 제목이 정상적으로 줄바꿈/생략되도록 하는 조합이 적절합니다.
src/components/group/RecruitingGroupBox.tsx (2)
91-93: Title 타이포그래피 토큰화 일관성 좋습니다글로벌 토큰으로 통일되어 테마 변경 시 유지보수가 쉬워집니다.
108-110: Tab 타이포그래피 토큰 적용 적절가벼운 텍스트에 regular/XS 조합이 시멘틱하게 맞습니다.
src/components/group/GroupCard.tsx (1)
24-29: 오버레이 조건 전환(group.isPublic === false) 도메인 검증 요청비공개 방만 잠금 오버레이를 노출하려는 의도로 보입니다. 다만 Group 타입에서 isPublic가 optional인 소스(예: 참여중인 방 변환)도 있을 수 있어 undefined인 경우의 기본 동작(오버레이 미노출)이 의도와 일치하는지 확인해 주세요.
필요 시 안전 가드 예:
- {group.isPublic === false && ( + {group.isPublic !== undefined && group.isPublic === false && ( <LockedOverlay>…</LockedOverlay> )}src/pages/groupDetail/GroupDetail.styled.ts (1)
153-176: BookHeader의 텍스트 트렁케이션/레이아웃 개선 좋습니다
- h3에 min-width: 0과 ellipsis 적용, img에 flex-shrink: 0 지정으로 레이아웃 깨짐과 오버플로우를 잘 방지했습니다. 의도대로 동작할 것으로 보입니다.
src/components/feed/BookInfoCard.tsx (1)
41-66: 디자인 토큰 전환 👍 (가독성과 일관성 개선)background, color, font-size/weight를 토큰으로 치환한 변경이 명확하고 일관적입니다. 유지보수성 향상에 도움이 됩니다.
src/components/group/MyGroupBox.tsx (1)
131-136: 내비게이션 경로 절대/상대 혼용 검토본 컴포넌트는 'detail/joined/...' 상대 경로를, MyGroupModal은 '/group/detail/joined/...' 절대 경로를 사용합니다. 라우터 베이스 경로에 따라 상대 경로가 의도와 다르게 해석될 수 있으니 통일 여부를 확인 부탁드립니다.
Also applies to: 156-158
src/components/group/MyGroupCard.tsx (1)
69-117: 디자인 토큰 치환 일괄 반영 좋습니다타이포/컬러 토큰으로의 치환이 일관되고, 다른 컴포넌트들과의 스타일 일관성을 높입니다.
src/components/group/MyGroupModal.tsx (1)
274-287: 스크롤바 숨김 처리와 그리드 구성 깔끔합니다모달 컨텍스트에 맞춘 시각적/UX 처리가 잘 되어 있습니다. 무한 스크롤과도 잘 맞물릴 것으로 보입니다.
Also applies to: 289-296
| useEffect(() => { | ||
| const fetchRooms = async () => { | ||
| try { | ||
| setIsLoading(true); | ||
| setError(null); | ||
| setNextCursor(null); | ||
| setIsLast(false); | ||
|
|
||
| const roomType: RoomType = | ||
| selected === '진행중' | ||
| ? 'playing' | ||
| : selected === '모집중' | ||
| ? 'recruiting' | ||
| : 'playingAndRecruiting'; | ||
|
|
||
| const res = await getMyRooms(roomType, null); | ||
| if (res.isSuccess) { | ||
| setRooms(res.data.roomList); | ||
| setNextCursor(res.data.nextCursor); | ||
| setIsLast(res.data.isLast); | ||
| } else { | ||
| setError(res.message); | ||
| } | ||
| } catch (e) { | ||
| console.log(e); | ||
| setError('방 목록을 불러오는데 실패했습니다.'); | ||
| } finally { | ||
| setIsLoading(false); | ||
| } | ||
| }; | ||
|
|
||
| fetchRooms(); | ||
| }, [selected]); | ||
|
|
There was a problem hiding this comment.
중복 데이터 로딩 useEffect로 인한 중복 호출/레이스 컨디션
선택 탭 변경 시 방 목록을 가져오는 useEffect가 동일 로직으로 두 번 존재합니다(라인 47-80, 122-155). 현재 구조는 API를 중복 호출하고 상태 업데이트 순서가 뒤엉킬 수 있습니다. 아래와 같이 두 번째 useEffect를 제거해 주세요.
- useEffect(() => {
- const fetchRooms = async () => {
- try {
- setIsLoading(true);
- setError(null);
- setNextCursor(null);
- setIsLast(false);
-
- const roomType: RoomType =
- selected === '진행중'
- ? 'playing'
- : selected === '모집중'
- ? 'recruiting'
- : 'playingAndRecruiting';
-
- const res = await getMyRooms(roomType, null);
- if (res.isSuccess) {
- setRooms(res.data.roomList);
- setNextCursor(res.data.nextCursor);
- setIsLast(res.data.isLast);
- } else {
- setError(res.message);
- }
- } catch (e) {
- console.log(e);
- setError('방 목록을 불러오는데 실패했습니다.');
- } finally {
- setIsLoading(false);
- }
- };
-
- fetchRooms();
- }, [selected]);📝 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.
| useEffect(() => { | |
| const fetchRooms = async () => { | |
| try { | |
| setIsLoading(true); | |
| setError(null); | |
| setNextCursor(null); | |
| setIsLast(false); | |
| const roomType: RoomType = | |
| selected === '진행중' | |
| ? 'playing' | |
| : selected === '모집중' | |
| ? 'recruiting' | |
| : 'playingAndRecruiting'; | |
| const res = await getMyRooms(roomType, null); | |
| if (res.isSuccess) { | |
| setRooms(res.data.roomList); | |
| setNextCursor(res.data.nextCursor); | |
| setIsLast(res.data.isLast); | |
| } else { | |
| setError(res.message); | |
| } | |
| } catch (e) { | |
| console.log(e); | |
| setError('방 목록을 불러오는데 실패했습니다.'); | |
| } finally { | |
| setIsLoading(false); | |
| } | |
| }; | |
| fetchRooms(); | |
| }, [selected]); |
🤖 Prompt for AI Agents
src/components/group/MyGroupModal.tsx around lines 122 to 155, there is a
duplicate useEffect that fetches rooms causing double API calls and race
conditions; remove this second useEffect block entirely and ensure any unique
logic it contained is merged into the existing useEffect at lines ~47-80 (adjust
dependencies to include selected if not already), so room fetching runs only
once per selection change; after removal, run the app/tests to verify no missing
behavior or regressions.
fix: QA 2차 수정
Summary by CodeRabbit
New Features
Bug Fixes
Style