Conversation
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
|
Caution Review failedThe pull request is closed. Walkthrough채팅 메시지 삭제 흐름에 removeMessage 콜백을 도입하고 훅/소켓/컴포넌트에 연결했습니다. 공유 UI에 Changes
Sequence Diagram(s)sequenceDiagram
actor User as User
participant UI as Comment Component
participant Hook as useDeleteChatMessage
participant API as Chat API
participant Socket as useChatSocket (removeMessage)
User->>UI: 삭제 버튼 클릭
UI->>Hook: mutate(messageId)
Hook->>API: DELETE /messages/:id
API-->>Hook: 200 OK
Hook->>Hook: invalidate chat-history
Hook-->>Socket: removeMessage(messageId)
Socket->>Socket: messages 상태에서 id 제거
UI-->>User: 삭제 반영
sequenceDiagram
participant Layout as PlaylistLayout
participant ActionBar as ActionBar
participant Share as ShareButton
participant Cd as Cd (variant="share")
Layout->>ActionBar: stickers = data?.cdItems ?? onlyCdResponse?.cdItems ?? []
ActionBar->>Share: stickers 전달
Share->>Cd: stickers 전달하여 렌더
Cd-->>Share: 스티커 포함 뷰 렌더
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Assessment against linked issues
Out-of-scope changes
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 💡 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
Status, Documentation and Community
|
|
🎵 Storybook Link 🎵 |
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/features/chat/model/sendMessage.ts (1)
24-26: roomId 변경/cleanup 시 재연결 누락 및 연결 상태 불일치 가능성
clientRef.current?.active가드로 인해 roomId 변경 타이밍에 새 연결이 생략될 수 있고, cleanup에서connected를 false로 되돌리지 않습니다. onDisconnect 핸들러와 함께 가드를 제거하고 cleanup 시 ref를 null로 초기화하는 편이 안전합니다.useEffect(() => { if (!roomId) return - if (clientRef.current?.active) return // 이미 활성화된 연결 방지 @@ - return () => { - client.deactivate() - } + client.onDisconnect = () => { + setConnected(false) + } + client.onStompError = () => { + setConnected(false) + } + + return () => { + setConnected(false) + client.deactivate() + clientRef.current = null + }Also applies to: 51-54
src/widgets/chat/ChatBottomSheet.tsx (2)
69-74: 버그: 소유자(role) 판별 로직이 반대로 작동현재는 “내가 방장일 때” 내 메시지가 아닌 모든 메시지가 owner로 표시됩니다. 메시지의 발신자가
creatorId인 경우에만 owner가 되어야 합니다.- if (msg.senderId === userData?.userId) { - role = 'mine' - } else if (userData?.userId === creatorId) { - role = 'owner' - } + if (msg.senderId === userData?.userId) { + role = 'mine' + } else if (msg.senderId === creatorId) { + role = 'owner' + }
39-41: 중복 메시지 제거 및 sentAt 미존재 대비 정렬 가드히스토리와 소켓 병합 시 중복이 생길 수 있고,
sentAt!단언은 빈값일 때 NaN을 유발합니다.messageId기준 dedupe + 안전한 정렬을 권장합니다.- const allMessages = [...historyMessages, ...socketMessages].sort( - (a, b) => new Date(a.sentAt!).getTime() - new Date(b.sentAt!).getTime() - ) + const merged = [...historyMessages, ...socketMessages] + const allMessages = Array.from(new Map(merged.map((m) => [m.messageId, m])).values()).sort( + (a, b) => (new Date(a.sentAt ?? 0).getTime()) - (new Date(b.sentAt ?? 0).getTime()) + )
🧹 Nitpick comments (14)
src/pages/home/ui/LoopCarousel.tsx (1)
183-186: 후속 개선: 타입 셀렉터 대신 컴포넌트 래핑으로 명시적 오버라이드후손 셀렉터(
button)는 디자인 시스템(Button)의 타이포 설정과 충돌하거나 렌더 태그 변경 시(예: a/div) 무력화될 수 있습니다. Button을 래핑한 styled 컴포넌트로 명시적으로 오버라이드하는 방식을 권장합니다. 가능하면 테마 토큰(예: theme.FONT_WEIGHT.medium)이 있으면 그 값을 사용해 매직 넘버를 제거하세요.아래와 같이 현재 범위에서는 후손 셀렉터를 제거하세요:
- button { - font-weight: 500; - }그리고 파일 바깥 변경(참고용) — Button 래핑 및 사용 교체:
// 1) styled 정의 const FirstSlideButton = styled(Button)` font-weight: 500; ` // 2) JSX 교체 // <Button ...>{...}</Button> <FirstSlideButton size="S" state="primary" onClick={() => (isLogin ? navigate('/mypage/customize') : navigate('/login'))} > {isLogin ? BUTTON_TEXT.MEMBER : BUTTON_TEXT.GUEST} </FirstSlideButton>src/pages/discover/playlist/index.tsx (1)
46-46: null 병합 연산자로 안전하게 기본값 처리 권장빈 배열도 falsy로 처리되는
||대신??를 사용해 의도치 않은 덮어쓰기를 방지하는 편이 안전합니다.- stickers={playlistData?.onlyCdResponse?.cdItems || []} + stickers={playlistData?.onlyCdResponse?.cdItems ?? []}src/widgets/playlist/PlaylistLayout.tsx (1)
110-111: ActionBar로 전달하는 stickers의 참조 안정화렌더마다 새로운 배열 리터럴이 생성되어 하위 컴포넌트가 불필요하게 리렌더될 수 있습니다. 상단에서
useMemo로 안정화해 전달하세요.@@ - <ActionBar + <ActionBar playlistId={data.playlistId} isFollowing={!!isFollowing} userName={data.creator.creatorNickname} showFollow={type !== 'My'} creatorId={data.creator.creatorId} - stickers={data?.cdItems ?? data?.onlyCdResponse?.cdItems ?? []} + stickers={stickers} />컴포넌트 상단(반환부 위)에 다음을 추가:
+ const stickers = useMemo( + () => data?.cdItems ?? data?.onlyCdResponse?.cdItems ?? [], + [data] + )src/widgets/playlist/ActionBar.tsx (1)
20-31: 옵셔널 stickers의 기본값 지정으로 분기 제거
ShareButton까지 동일한 기본값([])을 유지하면 분기 최소화와 타입 일관성에 도움이 됩니다.interface ActionBarProps { @@ - stickers?: CdCustomData[] + stickers?: CdCustomData[] } @@ - stickers, + stickers = [], }: ActionBarProps) => {src/pages/discover/index.tsx (2)
97-106: 초기화 가드(isReady)로 초기 진입 지연 가능성 — 부분 완화 제안
shuffleData가 늦게 도착하면 상세가 준비돼도 초기 재생이 지연됩니다. 상세만 준비되면 우선 초기화하고, 이후 셔플 데이터를 합치는 흐름을 권장합니다.- const isReady = !!playlistAsInfo && shuffleData !== undefined + const isReady = !!playlistAsInfo @@ - if (!currentPlaylist && playlistsData.length > 0 && isReady) { + if (!currentPlaylist && isReady && playlistsData.length > 0) {또는
isReady를 deps에 포함시켜 의도 명확성 확보:-}, [playlistsData, currentPlaylist, playlistId, setPlaylist]) +}, [playlistsData, currentPlaylist, playlistId, setPlaylist, isReady])
128-133: 뷰 카운트 5초 폴링 — 가시성/백그라운드 제어 권장탭 비활성화 시 불필요한 호출을 줄이기 위해
document.visibilityState기반 정지/재개를 권장합니다.- const viewCountTimer = setInterval(() => { - if (isPlaying) { - refetchViewCounts() - } - }, 5000) + const tick = () => { + if (document.visibilityState === 'visible' && isPlaying) { + refetchViewCounts() + } + } + const viewCountTimer = setInterval(tick, 5000)src/features/share/ui/ShareButton.tsx (3)
10-21: stickers 기본값 지정 및 prop 안정성 강화
stickers에 기본값을 지정하면Cd에 항상 배열이 전달되어 방어 코드가 단순해집니다.-export interface ShareButtonProps { +export interface ShareButtonProps { playlistId: number stickers?: CdCustomData[] } -const ShareButton = ({ playlistId, stickers }: ShareButtonProps) => { +const ShareButton = ({ playlistId, stickers = [] }: ShareButtonProps) => {
26-37: html-to-image 사용 시 CORS/캐시 문제 대응 옵션 추가 제안외부 이미지가 포함된 스티커는 캔버스 오염으로 실패할 수 있습니다.
cacheBust: true등 옵션 적용을 고려하세요.- const dataUrl = await toPng(currentRef) + const dataUrl = await toPng(currentRef, { cacheBust: true, pixelRatio: 2 })
26-37: slides 메모이제이션(경미한 최적화)
slides는 렌더마다 새로 생성됩니다. 빈번한 리렌더를 줄이기 위해useMemo적용을 고려할 수 있습니다. 영향이 미미하다면 현 상태도 무방합니다.- const slides = [ + const slides = useMemo(() => ([ { id: 'cd', content: <Cd variant="share" bgColor="none" stickers={stickers} /> }, { id: 'member', content: <img src={MemberCharacter} alt="Member Character" width={220} height={220} />, }, { id: 'guest', content: <img src={GuestCharacter} alt="Guest Character" width={220} height={220} />, }, - ] + ]), [stickers])src/features/chat/model/sendMessage.ts (1)
28-30: 하드코딩된 WS URL을 환경변수로 분리배포 환경별 엔드포인트를
.env로 관리하세요. Vite 기준 예시:- webSocketFactory: () => new SockJS('https://api.deulak.com/chat/ws') as unknown as WebSocket, + webSocketFactory: () => + new SockJS(import.meta.env.VITE_CHAT_WS_URL) as unknown as WebSocket,src/features/chat/model/useChat.ts (1)
31-37: 히스토리 캐시도 낙관적 삭제하여 깜빡임/지연 최소화현재는 무효화만 하므로 재요청 전까지 히스토리 영역에 항목이 남아 있을 수 있습니다.
onMutate로 낙관적 제거 후 에러 시 롤백하는 패턴을 권장합니다.return useMutation({ mutationFn: (messageId: string) => deleteChatMessage(roomId, messageId), - onSuccess: (_, messageId) => { - queryClient.invalidateQueries({ queryKey: ['chat-history', roomId] }) - removeMessage(messageId) // 소켓 state 업데이트 - }, + onMutate: async (messageId: string) => { + await queryClient.cancelQueries({ queryKey: ['chat-history', roomId] }) + const prev = queryClient.getQueryData<InfiniteData<ChatHistoryResponse>>([ + 'chat-history', + roomId, + ]) + // 히스토리 캐시에서 낙관적 제거 + queryClient.setQueryData<InfiniteData<ChatHistoryResponse>>( + ['chat-history', roomId], + (data) => + data && { + pageParams: data.pageParams, + pages: data.pages.map((p) => ({ + ...p, + messages: p.messages.filter((m) => m.messageId !== messageId), + })), + } + ) + // 소켓 로컬 상태도 즉시 제거 + removeMessage(messageId) + return { prev } + }, + onError: (_err, _messageId, ctx) => { + if (ctx?.prev) { + queryClient.setQueryData(['chat-history', roomId], ctx.prev) + } + }, + onSettled: () => { + queryClient.invalidateQueries({ queryKey: ['chat-history', roomId] }) + }, })src/widgets/chat/ChatBottomSheet.tsx (2)
53-55: 스크롤 최상단 판별 엄격도 완화부동소수/서브픽셀 영향으로
=== 0비교가 불안정할 수 있습니다.<= 0또는 임계값(e.g. <= 2) 비교를 권장합니다.- if (scrollRef.current.scrollTop === 0 && hasNextPage && !isFetchingNextPage) { + if (scrollRef.current.scrollTop <= 0 && hasNextPage && !isFetchingNextPage) {
58-58: 디버그 로그 제거불필요한 콘솔 로그는 제거해주세요.
- console.log(allMessages) + // console.log(allMessages)src/entities/comment/ui/Comment.tsx (1)
46-49: 삭제 실패 피드백 추가 권장에러 시 사용자 피드백이 없습니다. onError 토스트를 추가하세요.
- deleteMessage(messageId, { - onSuccess: () => toast('COMMENT'), - }) + deleteMessage(messageId, { + onSuccess: () => toast('COMMENT'), + onError: () => toast('ERROR'), + })
📜 Review details
Configuration used: Path: .coderabbit.yaml
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 selected for processing (10)
src/entities/comment/ui/Comment.tsx(2 hunks)src/features/chat/model/sendMessage.ts(1 hunks)src/features/chat/model/useChat.ts(1 hunks)src/features/share/ui/ShareButton.tsx(1 hunks)src/pages/discover/index.tsx(2 hunks)src/pages/discover/playlist/index.tsx(1 hunks)src/pages/home/ui/LoopCarousel.tsx(1 hunks)src/widgets/chat/ChatBottomSheet.tsx(2 hunks)src/widgets/playlist/ActionBar.tsx(4 hunks)src/widgets/playlist/PlaylistLayout.tsx(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js,jsx}
⚙️ CodeRabbit configuration file
**/*.{ts,tsx,js,jsx}: ## 1. 일반적인 코딩 컨벤션포맷팅
.prettierrc설정에 따라 포맷팅 확인- 들여쓰기: 2칸 스페이스
- 최대 줄 길이: 100자
- 세미콜론 사용 안함
- 따옴표: 작은따옴표 사용
- 괄호 안 공백: 있음
- 화살표 함수 괄호: 항상 사용
- 줄바꿈: LF 사용
네이밍 컨벤션
- 컴포넌트: PascalCase (예: UserProfile)
- 유틸리티/훅/변수: camelCase (예: getUserData, useUserInfo)
- 상수: UPPER_SNAKE_CASE (예: API_BASE_URL)
- 이미지 파일: kebab-case (예: user-profile-icon.png)
주석 사용
- 복잡한 로직에만 주석 추가
- 불필요한 주석 지양 (코드로 설명 가능한 것)
- TODO/FIXME 형식:
// TODO: 설명 - 작성자가독성
- 매직 넘버 지양, 의미있는 상수 사용
- 함수는 하나의 책임만 가지도록 작성 (최대 20줄 권장)
- 중첩 깊이 최소화 (3단계 이하 권장)
2. React 모범 사례
컴포넌트 작성
- 최신 React hooks 사용 권장
- 컴포넌트는 단일 책임 원칙 준수
- Presentational/Container 컴포넌트 분리
- 성능 최적화: memo, useCallback, useMemo 적절히 사용
- 대용량 리스트는 가상화 라이브러리 사용 고려
상태 관리
- Zustand와 Tanstack Query를 일관되게 사용
- 상태 구조는 정규화된 형태로 관리
- 에러 처리: Error Boundary와 try-catch 또는 onError 콜백 활용
3. 스타일링
Styled Components
- Styled Components 일관되게 사용
- 스타일드 컴포넌트명은 의미있게 작성
- 동적 스타일링은 props나 CSS 변수 활용
- 테마 시스템 활용하여 글로벌 스타일 관리
- 재사용 가능한 스타일은 mixin이나 확장으로 관리
- CSS 포맷팅 가독성 유지
- 사용하지 않는 스타일이나 중복 스타일 제거
4. Vite 및 빌드 최적화
- 모듈 import 최적화 (tree-shaking 고려)
- 환경변수는 .env 파일로 관리
- vite.config.ts에서 빌드 성능 튜닝 (sourcemap 설정, 플러그인 최적화 등)
5. 아키텍처 및 개발 환경
폴더 구조
- Feature-Sliced Design (FSD) 구조 준수
- 레이어별 참조 규칙 엄격히 적용
타입스크립트
- strict 모드 사용
- 타입 명시적으로 작성 (any 사용 지양)
- path alias (@/_) 절대 경로 import 사용
Git 훅
- Husky 설정으로 pre-commit, commit-msg 린팅 확인
6. 기타 가이드라인
- 충분한 근거와 함께 리뷰 제공
- 정보 검증 후 답변
- 간결하고 명확한 응답
- 필요시 추가 컨텍스트 요청
- 검증되지 않은 주장 지양
- 가능한 경우 출처 명시
- 별도 언급 없으면 JavaScript 기준
- 한국어로 응답
- 대부분 브라우저에서 지원하는 ES6+ 기능 활용
- 코드 리뷰를 통한 유지보수성 향상에 적극 활용
Files:
src/pages/discover/playlist/index.tsxsrc/features/chat/model/sendMessage.tssrc/features/chat/model/useChat.tssrc/pages/home/ui/LoopCarousel.tsxsrc/widgets/playlist/ActionBar.tsxsrc/pages/discover/index.tsxsrc/widgets/playlist/PlaylistLayout.tsxsrc/features/share/ui/ShareButton.tsxsrc/widgets/chat/ChatBottomSheet.tsxsrc/entities/comment/ui/Comment.tsx
🧬 Code graph analysis (5)
src/features/chat/model/useChat.ts (1)
src/features/chat/api/chat.ts (1)
deleteChatMessage(10-12)
src/widgets/playlist/ActionBar.tsx (1)
src/entities/playlist/types/playlist.ts (1)
CdCustomData(23-34)
src/features/share/ui/ShareButton.tsx (1)
src/entities/playlist/types/playlist.ts (1)
CdCustomData(23-34)
src/widgets/chat/ChatBottomSheet.tsx (1)
src/features/chat/model/sendMessage.ts (1)
useChatSocket(17-80)
src/entities/comment/ui/Comment.tsx (1)
src/features/chat/model/useChat.ts (1)
useDeleteChatMessage(28-38)
🔇 Additional comments (10)
src/pages/home/ui/LoopCarousel.tsx (1)
183-186: LGTM — 버튼 폰트 굵기 보정 목표 충족FirstContent 하위의 버튼 폰트 굵기를 500으로 고정하는 목적에 부합합니다. 다만 이 선택자는 실제 Button 컴포넌트가 HTML button 요소로 렌더링될 때만 적용됩니다. 확인 부탁드립니다.
src/widgets/playlist/PlaylistLayout.tsx (1)
105-111: creatorId 타입 일치 여부 확인 및 문자열 강제 변환 고려
ActionBar의creatorId가 string으로 정의되어 있을 가능성이 높습니다.data.creator.creatorId가 number라면 런타임 불일치가 생길 수 있으니 명시적 변환 또는 Prop 타입 조정을 검토하세요.- creatorId={data.creator.creatorId} + creatorId={String(data.creator.creatorId)}src/widgets/playlist/ActionBar.tsx (2)
6-6: 타입 import 경로 확인
@/entities/playlist가CdCustomData를 재export하지 않으면 빌드가 실패합니다. 실제 export 경로(@/entities/playlist/types/playlist)를 확인해 주세요.
52-53: ShareButton으로의 데이터 전달 LGTM공유 이미지에 필요한 CD 스티커 데이터가 적절히 전달됩니다.
src/pages/discover/index.tsx (1)
123-126: confirm 타이밍 5초로 단축 — 서버 규칙과 일치 여부 확인뷰 처리·중복 집계 정책과 맞는지 확인이 필요합니다. 너무 짧으면 의도치 않은 confirm 남발이 될 수 있습니다.
src/features/chat/model/sendMessage.ts (1)
75-77: LGTM: 로컬 소켓 상태에서 메시지 제거 콜백 추가로 삭제 UX 개선정확히
messageId기준으로 필터링하고,useCallback의 함수형 업데이트 사용도 적절합니다.src/features/chat/model/useChat.ts (1)
28-28: LGTM: 삭제 훅 시그니처 확장 및 성공 시 소켓 상태 싱크
onSuccess에서variables로 넘어온messageId를 사용해 소켓 측 상태를 정리하는 흐름이 명확합니다.Also applies to: 33-36
src/widgets/chat/ChatBottomSheet.tsx (1)
27-27: LGTM: 삭제 콜백 전달 경로 연결 완료
useChatSocket에서 받은removeMessage를Comment에 프롭으로 전달하는 연결이 일관됩니다.Also applies to: 86-87
src/entities/comment/ui/Comment.tsx (2)
18-19: LGTM: 삭제 흐름 프롭 추가 및 훅 시그니처 반영
removeMessage프롭 추가와useDeleteChatMessage(roomId, removeMessage)적용이 정상적으로 연결되었습니다.Also applies to: 42-43
120-122: 디자인 확인 요청: 삭제/신고 색상 매핑현재 'delete'는 회색, 'report'는 에러 색입니다. 통상 삭제가 파괴적 액션으로 강조색(에러)을 씁니다. 의도된 디자인인지 확인 부탁드립니다.
🛰️ 관련 이슈
✨ 주요 변경 사항
🔍 테스트 방법 / 체크리스트
🗯️ PR 포인트
🚀 알게된 점
📖 참고 자료 (선택)
Summary by CodeRabbit