Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Walkthroughrooms API의 catch 타입을 unknown으로 변경하고 AxiosError 및 응답 코드(100004, 140011)를 검사해 명시적 오류를 던지도록 수정했습니다. 관련 페이지들은 해당 메시지에 따라 네비게이션 리다이렉트를 추가했습니다. 그룹 생성 UI에 여러 구분선과 기본 종료일(+2일) 변경, 섹션 클릭 영역 확대, 게시글 업데이트에서 remainImageUrls 항상 포함 등이 적용되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant U as 사용자
participant Page as GroupDetail 페이지
participant API as getRoomDetail()
participant S as 서버
U->>Page: 그룹 상세 진입
Page->>API: 방 상세 조회 요청
API->>S: GET /rooms/{id}
S-->>API: 200 OK / 에러(code)
alt 200 OK
API-->>Page: data
Page-->>U: 상세 렌더링
else 에러 code=100004
API-->>Page: Error("모집기간이 만료된 방입니다.")
Page->>Page: navigate(`/group/detail/joined/${id}`, replace=true)
else 에러 code=140011
API-->>Page: Error("방 접근 권한이 없습니다.")
Page->>Page: navigate('/group', replace=true)
else 기타
API-->>Page: 원본 에러 재throw
Page-->>U: setError(...) 및 에러 표시
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
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. ✨ 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: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/components/creategroup/ActivityPeriodSection/ActivityPeriodSection.tsx (2)
88-96: 기본 종료일 계산이 startDate를 고려하지 않아 자동 보정이 깨질 수 있습니다현재 getInitialEndDate는 무조건 "오늘+2일"을 반환합니다. startDate가 오늘+2일보다 더 미래인 경우(예: 시작일이 10일 뒤) 종료일이 시작일보다 과거로 자동 보정될 수 있어 초기·재보정 로직이 깨집니다.
- 재현 예: 시작일=9/10, 사용자가 종료일을 9/5로 선택 → 보정이 오늘+2일로 되는데, 여전히 시작일(9/10)보다 과거일 수 있습니다.
- 기대: 최소 종료일은 max(오늘+2일, 시작일) 이어야 합니다.
아래처럼 기본 종료일을 startDate와 정책(오늘+2) 중 더 늦은 값으로 계산해주세요.
- const getInitialEndDate = () => { - const tomorrow = new Date(); - tomorrow.setDate(tomorrow.getDate() + 2); - return { - year: tomorrow.getFullYear(), - month: tomorrow.getMonth() + 1, - day: tomorrow.getDate(), - }; - }; + const getInitialEndDate = () => { + const today = new Date(); + today.setHours(0, 0, 0, 0); + + const policyMinEnd = new Date(today); + policyMinEnd.setDate(policyMinEnd.getDate() + 2); // 기본 정책: 오늘+2 + + const start = new Date(startDate.year, startDate.month - 1, startDate.day); + start.setHours(0, 0, 0, 0); + + const minEnd = policyMinEnd.getTime() < start.getTime() ? start : policyMinEnd; + return { + year: minEnd.getFullYear(), + month: minEnd.getMonth() + 1, + day: minEnd.getDate(), + }; + };
120-129: 종료일 자동 보정 시 기준 날짜를 ‘max(오늘+2, 시작일)’로 통일하세요현재 종료일 검증은 “내일(+1)”과 “시작일”만 비교하고, 보정은 getInitialEndDate(오늘+2)로 되돌립니다. 이 불일치로 시작일이 오늘+2보다 미래인 경우 보정 후에도 여전히 무효가 될 수 있습니다. 검증/보정 모두 동일한 기준(minAllowedEnd)을 사용하도록 수정이 필요합니다.
- if (isEndDate) { - const startDateObj = new Date(startDate.year, startDate.month - 1, startDate.day); - const tomorrow = new Date(); - tomorrow.setDate(tomorrow.getDate() + 1); - - // 종료일이 내일보다 이르거나 시작일보다 이른 경우 조정 - if (selectedDate < tomorrow || selectedDate < startDateObj) { - adjustedDate = getInitialEndDate(); - } - } + if (isEndDate) { + const startDateObj = new Date(startDate.year, startDate.month - 1, startDate.day); + startDateObj.setHours(0, 0, 0, 0); + + const today = new Date(); + today.setHours(0, 0, 0, 0); + const policyMinEnd = new Date(today); + policyMinEnd.setDate(policyMinEnd.getDate() + 2); // 정책 기준일 + + const minAllowedEnd = + policyMinEnd.getTime() < startDateObj.getTime() ? startDateObj : policyMinEnd; + + if (selectedDate < minAllowedEnd) { + adjustedDate = { + year: minAllowedEnd.getFullYear(), + month: minAllowedEnd.getMonth() + 1, + day: minAllowedEnd.getDate(), + }; + } + }src/components/memory/RecordItem/RecordItem.tsx (1)
133-135: roomId 기본값 '1' 강제는 위험합니다(오삭제/오핀 유발 가능)
roomId || '1'로 기본값을 강제하면, URL 파라미터가 비어있는 경우 다른 방(혹은 존재하지 않는 방) 대상으로 삭제/핀하기 요청이 나갈 수 있습니다. 이는 고위험 동작입니다. roomId가 없으면 즉시 사용자에게 알리고 조기 반환하세요.- const currentRoomId = roomId || '1'; + if (!roomId) { + openSnackbar({ + message: '유효하지 않은 방입니다.', + variant: 'top', + onClose: () => {}, + }); + return; + } - const recordId = parseInt(record.id); + const recordId = Number(record.id); + if (Number.isNaN(recordId)) { + openSnackbar({ message: '유효하지 않은 기록 ID입니다.', variant: 'top', onClose: () => {} }); + return; + } - if (type === 'poll') { - response = await deleteVote(parseInt(currentRoomId), recordId); - } else { - response = await deleteRecord(parseInt(currentRoomId), recordId); - } + if (type === 'poll') { + response = await deleteVote(Number(roomId), recordId); + } else { + response = await deleteRecord(Number(roomId), recordId); + }핀하기 로직에도 동일하게 적용해 주세요:
- const currentRoomId = roomId || '1'; - const recordId = parseInt(record.id); + if (!roomId) { + openSnackbar({ message: '유효하지 않은 방입니다.', variant: 'top', onClose: () => {} }); + return; + } + const recordId = Number(record.id); + if (Number.isNaN(recordId)) { + openSnackbar({ message: '유효하지 않은 기록 ID입니다.', variant: 'top', onClose: () => {} }); + return; + } - const response = await pinRecordToFeed(parseInt(currentRoomId), recordId); + const response = await pinRecordToFeed(Number(roomId), recordId); @@ - roomId: currentRoomId, + roomId, // 위에서 roomId 존재 확인 완료Also applies to: 140-143, 193-195, 197-198, 211-214
🧹 Nitpick comments (25)
src/components/group/CommentSection.tsx (1)
21-21: alt 텍스트 표현 미세 조정이미지는 직접 클릭 대상이 아니므로 ‘버튼’이라는 용어는 혼선을 줄 수 있습니다. ‘아이콘’으로 바꾸는 것을 제안합니다.
- <CommentSectionChevron src={rightChevron} alt="한마디 이동 버튼" /> + <CommentSectionChevron src={rightChevron} alt="한마디 이동 아이콘" />src/components/group/RecordSection.styled.ts (1)
57-59: progress 값 클램프 제안 — 스타일 파손 예방
width: ${progress}%는 0~100을 벗어나면 레이아웃이 깨질 수 있습니다. 안전하게 클램프하세요.-export const ProgressBarFill = styled.div<{ progress: number }>` - width: ${({ progress }) => progress}%; +export const ProgressBarFill = styled.div<{ progress: number }>` + width: ${({ progress }) => Math.max(0, Math.min(100, progress))}%; height: 100%; background-color: ${colors.purple.main}; border-radius: 4px; transition: width 0.3s ease;src/components/group/RecordSection.tsx (1)
26-26: alt 텍스트 용어 정교화이미지는 내비게이션을 암시하는 장식 아이콘이므로 ‘버튼’ 대신 ‘아이콘’이 더 정확합니다.
- <RecordSectionChevron src={rightChevron} alt="기록장 이동 버튼" /> + <RecordSectionChevron src={rightChevron} alt="기록장 이동 아이콘" />src/components/creategroup/ActivityPeriodSection/ActivityPeriodSection.tsx (3)
103-106: 날짜 비교 시 시각(시간대) 영향을 제거해 경계 버그를 예방하세요Date 비교 전에 모두 00:00:00.000으로 정규화하지 않으면 자정 경계/타임존에 따라 오탐 보정이 발생할 수 있습니다. 아래와 같이 정규화 적용을 권장합니다.
- const today = new Date(); - const selectedDate = new Date(date.year, date.month - 1, date.day); + const today = new Date(); + today.setHours(0, 0, 0, 0); + const selectedDate = new Date(date.year, date.month - 1, date.day); + selectedDate.setHours(0, 0, 0, 0);또한 start/endDate를 생성하는 다른 지점(예: Line 53-55, 63-65)에서도 동일한 정규화를 적용하면 일관성이 높아집니다.
135-166: 중복된 “시작일 변경 시 종료일 재검증” 로직을 유틸로 추출하세요handleStartYear/Month/Day 세 곳에서 동일한 재검증 패턴을 반복합니다. 작은 유틸(ensureValidEndDate 등)로 추출하면 유지보수성과 테스트 용이성이 좋아집니다.
59-60: “최대 3개월” 카피와 90일 하드코딩의 정책 정합성 확인 필요문구는 “최대 3개월”인데 로직은 90일 고정입니다. 31일이 포함되는 구간에서는 실제 3개월이 92~93일이 될 수 있습니다. 정책이 “달 기준”인지 “일수 기준”인지 확인 부탁드립니다. 필요 시 “달 기준” 계산으로 전환하거나 카피를 “최대 90일”로 맞추는 것이 깔끔합니다.
src/components/creategroup/MemberLimitSection.tsx (1)
20-21: Section 중첩 대신 상위 Section에 showDivider를 주입하는 방식으로 단순화하세요현재
내부에 를 중첩 사용하고 있어 DOM/여백이 불필요하게 늘어날 수 있고, styled-section이 실제 태그라면 시맨틱 중첩 이슈가 생깁니다. 상위 Section에 showDivider 속성을 주는 쪽이 간결합니다. - <Section> - <Section showDivider /> + <Section showDivider>만약 시각적 구분선만 필요하면 전용 컴포넌트를 쓰거나 CSS pseudo-element로 처리하는 것도 좋습니다.
src/components/creategroup/PrivacySettingSection/PrivacySettingSection.tsx (1)
27-28: Privacy 섹션도 Section 중첩 없이 처리 권장UI 목적의 구분선이라면 상위 Section에 showDivider를 부여하는 방식이 더 간단하고 예측 가능하며, 시맨틱 중첩 리스크도 피할 수 있습니다.
- <Section> - <Section showDivider /> + <Section showDivider>추가로 ToggleSwitch가 div 기반이라면 role="switch" 및 aria-checked 적용을 고려하면 접근성이 향상됩니다. (선택사항)
src/api/rooms/getRoomPlaying.ts (2)
71-74: 메시지 문자열 비교 대신 도메인 에러로 표준화하는 것을 고려하세요다른 API들(getRoomDetail/getRoomMembers 등)과 동일한 에러 코드(140011 등)를 처리 중이라면, 코드→의미 매핑을 공통화하고 Error 서브클래스(예: PermissionDeniedError, RecruitmentExpiredError)로 던지면 페이지단 라우팅 분기가 메시지 문자열에 의존하지 않아 안정적입니다. i18n 변경에도 견고해집니다.
- 공통 헬퍼 예시: mapApiErrorCodeToDomainError(axiosError)
- 페이지단 분기: if (err instanceof PermissionDeniedError) navigate('/group')
52-62: index 기반 id 생성은 리스트 재정렬 시 불안정할 수 있습니다convertVotesToPolls에서 id를 index로 생성하면 재정렬/필터링 시 키 불안정으로 React 성능 경고가 발생할 수 있습니다. 서버가 고유 식별자를 제공하지 않는다면 content+page 조합 등으로 안정 키를 생성하는 것을 고려해주세요. (선택사항)
src/components/creategroup/GenreSelectionSection.tsx (1)
15-16: 구분선 표현을 위해 Section 중첩 사용 대신 속성으로 처리여기도 동일하게 상위 Section에 showDivider를 부여하는 방식이 간결합니다. 불필요한 DOM 중첩을 줄이고 시맨틱 이슈를 예방합니다.
- <Section> - <Section showDivider /> + <Section showDivider>src/api/rooms/getRoomMembers.ts (1)
50-52: API 레이어에서의 console.error 남발 가능성API 유틸 내부에서
console.error를 직접 호출하면 상위 호출부에서도 로깅할 때 중복 로그가 발생할 수 있습니다. 호출부에서만 로깅하거나, 공용 로거로 레벨 제어를 권장합니다.src/components/creategroup/RoomInfoSection.tsx (1)
19-20: 불필요한 DOM 노드 최소화 제안(선택)
<Section showDivider />를 독립 노드로 세 번 추가하기보다,Section에topDivider / bottomDivider같은 prop을 두어 하나의 섹션 컴포지션으로 표현하면 DOM이 얇아지고 스타일 계산도 단순해집니다(특히 리스트가 많은 화면에서).Also applies to: 34-35, 50-51
src/pages/groupDetail/ParticipatedGroupDetail.tsx (1)
77-86: 권한 오류 리다이렉트 로직 공통화 제안여러 페이지에서 동일한 패턴(권한 오류 →
/group리다이렉트)이 반복됩니다.usePermissionRedirect(err)같은 훅으로 공통화하면 중복 제거 및 유지보수성이 좋아집니다.src/pages/groupMembers/GroupMembers.tsx (1)
26-33: 도달 불가능한 분기 제거 제안
const currentRoomId = roomId || localStorage.getItem('currentRoomId') || '1';로 기본값을'1'로 보장하고 있어, 직후의if (!currentRoomId)는 절대 진입하지 않습니다. 분기를 제거하거나, 기본값 제공을 상위로 이동해 의도를 명확히 하길 권장합니다.간단 제거 예시:
- if (!currentRoomId) { - setError('방 ID를 찾을 수 없습니다.'); - setLoading(false); - return; - }src/api/rooms/getRoomDetail.ts (1)
45-56: 문자열 비교 의존도 축소를 위한 에러 매핑 유틸 제안(선택)UI단에서
err.message === '...'비교 대신, API 계층에서code → DomainError로 매핑해error.name또는error.code기반 분기를 추천합니다. 예:throw new PermissionDeniedError()또는throw new ApiError(140011, '...').src/pages/groupDetail/GroupDetail.tsx (1)
103-115: 문자열 비교 기반 분기(에러 메시지)는 취약 — 에러 코드/커스텀 에러로 분기하도록 리팩터 권장현재 '모집기간이 만료된 방입니다.', '방 접근 권한이 없습니다.' 같은 하드코딩된 한글 문자열에 의존합니다. 메시지 변경(문구/띄어쓰기/i18n) 시 즉시 오동작합니다. API 래퍼에서 이미 에러 코드를 해석하고 있으니, 아래 중 하나로 개선을 권장합니다.
- 최소침습: 메시지 상수를 중앙화해 오타/변경에 대비
- 예: src/constants/roomErrors.ts에 상수 정의 후 사용
- 권장: API 래퍼에서 커스텀 에러 클래스로 throw하여 코드 기반으로 분기
- 예: new RoomError('RECRUITMENT_CLOSED') / new RoomError('NO_ACCESS')
- UI 단에서는 err instanceof RoomError && err.code === 'NO_ACCESS' 식으로 분기
- 중복 제거: ParticipatedGroupDetail.tsx와 동일 로직이 있으므로, 공통 유틸(handleRoomDomainError)로 추출
참고로 위 100라인의 diff와 함께 최소 수정으로 유지하려면, 아래처럼 message 변수 사용으로 NPE를 방지할 수 있습니다.
- // 모집기간이 만료된 방인 경우 - 진행중인 방으로 리다이렉트 - if (error.message === '모집기간이 만료된 방입니다.') { + // 모집기간이 만료된 방인 경우 - 진행중인 방으로 리다이렉트 + if (message === '모집기간이 만료된 방입니다.') { navigate(`/group/detail/joined/${roomId}`, { replace: true }); return; } - // 방 접근 권한이 없는 경우 - 모임 홈으로 리다이렉트 - if (error.message === '방 접근 권한이 없습니다.') { + // 방 접근 권한이 없는 경우 - 모임 홈으로 리다이렉트 + if (message === '방 접근 권한이 없습니다.') { navigate('/group', { replace: true }); return; }추가로 원천 해결안(별도 파일 예시):
// src/errors/RoomError.ts export type RoomErrorCode = 'RECRUITMENT_CLOSED' | 'NO_ACCESS'; export class RoomError extends Error { constructor(public code: RoomErrorCode, message?: string) { super(message ?? code); this.name = 'RoomError'; } } // src/api/rooms/getRoomDetail.ts // throw new RoomError('RECRUITMENT_CLOSED'); // code 100004 // throw new RoomError('NO_ACCESS'); // code 140011 // 사용처 catch (err) { if (err instanceof RoomError) { if (err.code === 'RECRUITMENT_CLOSED') { ... } if (err.code === 'NO_ACCESS') { ... } } }src/pages/post/UpdatePost.tsx (2)
121-127: 사진 추가/삭제 핸들러가 no-op입니다 — 의도라면 UI 비활성화, 아니라면 구현 필요편집 화면에서 새 사진 추가가 불가한 정책이라면 PhotoSection의 추가/삭제 UI를 비활성화하거나 힌트를 노출하세요. 반대로 추가 지원이 목적이면 업로드 핸들러를 구현해야 합니다.
가능한 방향:
- 편집에서 “추가” 비활성화
- 또는 업로드 훅(예: useUploadImages) 연동 및 UpdateFeedBody에 새 업로드 결과를 병합
116-118: 수정 실패 시 사용자 피드백 없음 — 스낵바로 실패 메시지 노출 권장네트워크/서버 오류 등 실패 시 즉시 반환만 하고 있어 UX가 불명확합니다. 스낵바로 실패 안내를 추가하세요.
적용 diff:
- if (!result?.success) { - return; - } + if (!result?.success) { + openSnackbar({ + message: result?.message ?? '피드 수정에 실패했습니다.', + variant: 'top', + onClose: closePopup, + }); + return; + }src/components/memory/RecordItem/RecordItem.tsx (6)
375-392: 텍스트 기록에만 핀 버튼 노출: 메뉴 정책과 UI 일관성 확보상단 더보기 메뉴에서 onPin을 텍스트에만 제공하도록 한 것과 UI 버튼 노출 정책이 일치합니다. 사용성 측면에서 혼란을 줄이는 적절한 제한입니다.
추가 제안(접근성):
ActionButton이 실제 버튼 역할이라면aria-label="피드에 핀하기"를 부여하고, div 기반이라면role="button"과tabIndex={0}를 추가해 키보드 접근성을 확보해 주세요.가능한 미니멀 변경 예:
- <ActionButton + <ActionButton + aria-label="피드에 핀하기" onClick={
76-83: 좋아요 카운트 동기화 로직 보강(서버 카운트 사용/하한 0 보장)서버에서 최신 likeCount를 내려주면 그것을 신뢰하는 편이 안전합니다. 없을 경우에도 카운트가 음수로 내려가지 않도록 하한을 두세요.
- setCurrentLikeCount((prev: number) => (response.data.isLiked ? prev + 1 : prev - 1)); + setCurrentLikeCount((prev: number) => + typeof response.data.likeCount === 'number' + ? response.data.likeCount + : Math.max(0, response.data.isLiked ? prev + 1 : prev - 1) + );
338-347: 연타(중복 요청) 방지: in-flight 상태로 클릭 잠그기빠른 연타 시 응답 경합으로 UI/서버 상태 불일치가 생길 수 있습니다. 좋아요 요청 동안 버튼을 잠그는 얇은 플래그를 두세요.
핵심 변경 요약:
- 상태 추가:
const [liking, setLiking] = useState(false);- 요청 전/후로
setLiking(true/false)- 버튼에서
pointerEvents/aria-busy조절예시:
- const handleLikeClick = async () => { + const handleLikeClick = async () => { + if (liking) return; + setLiking(true); try { const postId = parseInt(id); @@ } catch (error) { console.error('좋아요 API 호출 실패:', error); openSnackbar({ message: '네트워크 오류가 발생했습니다. 다시 시도해주세요.', variant: 'top', onClose: () => {}, }); } + finally { + setLiking(false); + } };- style={{ + style={{ cursor: shouldBlur ? 'default' : 'pointer', - pointerEvents: shouldBlur ? 'none' : 'auto', + pointerEvents: shouldBlur || liking ? 'none' : 'auto', }} + aria-busy={liking || undefined}컴포넌트 상단 어딘가에 상태를 추가하세요:
const [liking, setLiking] = useState(false);
153-156: window.location.reload() 대신 상위 상태 갱신으로 교체 권장전면 새로고침은 UX가 거칠고, 상태 관리 이점을 잃습니다. 상위(목록) 컴포넌트로 콜백을 올려 해당 아이템만 제거하는 방향이 바람직합니다.
가능하면
RecordItem에onDeleted?: (recordId: string) => void를 추가하고, 성공 시onDeleted?.(record.id)호출로 대체하도록 리팩터 제안 드립니다. 원하시면 관련 변경까지 한번에 패치 제안 드릴게요.
68-74: parseInt 기본 진수 미지정 및 반복 파싱 정리
parseInt는 반드시 10진을 명시하거나Number(...)를 사용하는 편이 안전합니다. 또한 같은 값을 여러 번 파싱하지 않도록 지역 변수로 묶어 재사용하세요.예시 변경:
- const postId = parseInt(id); + const postId = Number(id); + if (Number.isNaN(postId)) { /* 에러 처리 */ return; }- const response = await pinRecordToFeed(parseInt(currentRoomId), recordId); + const response = await pinRecordToFeed(Number(roomId), recordId);- openCommentBottomSheet(parseInt(id), type === 'poll' ? 'VOTE' : 'RECORD'); + openCommentBottomSheet(Number(id), type === 'poll' ? 'VOTE' : 'RECORD');- postId={parseInt(id)} + postId={Number(id)}Also applies to: 193-198, 265-266, 327-328
1-4: 타입 별칭 권장: 전역 Record<K, T>와의 혼동 방지
import type { Record } ...는 TS 내장Record<K, V>와 이름 충돌을 유발해 가독성이 떨어질 수 있습니다.MemoryRecord등 도메인 친화 이름으로 별칭하는 것을 권장합니다.-import type { Record } from '../../../types/memory'; +import type { Record as MemoryRecord } from '../../../types/memory'; @@ -const RecordItem = ({ record, shouldBlur = false }: RecordItemProps) => { +const RecordItem = ({ record, shouldBlur = false }: RecordItemProps) => {또는 원 타입 정의 자체를
MemoryRecord로 개명하는 것을 검토해 주세요(범위가 넓으므로 별도 PR 권장).
📜 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 selected for processing (17)
src/api/rooms/getRoomDetail.ts(1 hunks)src/api/rooms/getRoomMembers.ts(1 hunks)src/api/rooms/getRoomPlaying.ts(1 hunks)src/components/creategroup/ActivityPeriodSection/ActivityPeriodSection.tsx(1 hunks)src/components/creategroup/GenreSelectionSection.tsx(1 hunks)src/components/creategroup/MemberLimitSection.tsx(1 hunks)src/components/creategroup/PrivacySettingSection/PrivacySettingSection.tsx(1 hunks)src/components/creategroup/RoomInfoSection.tsx(2 hunks)src/components/group/CommentSection.styled.ts(1 hunks)src/components/group/CommentSection.tsx(1 hunks)src/components/group/RecordSection.styled.ts(1 hunks)src/components/group/RecordSection.tsx(1 hunks)src/components/memory/RecordItem/RecordItem.tsx(3 hunks)src/pages/groupDetail/GroupDetail.tsx(1 hunks)src/pages/groupDetail/ParticipatedGroupDetail.tsx(1 hunks)src/pages/groupMembers/GroupMembers.tsx(1 hunks)src/pages/post/UpdatePost.tsx(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/components/group/CommentSection.tsx (1)
src/components/group/CommentSection.styled.ts (1)
CommentSectionHeader(16-20)
src/components/group/RecordSection.tsx (1)
src/components/group/RecordSection.styled.ts (1)
RecordSectionHeader(16-20)
🪛 ESLint
src/pages/groupDetail/ParticipatedGroupDetail.tsx
[error] 77-77: Unexpected any. Specify a different type.
(@typescript-eslint/no-explicit-any)
src/api/rooms/getRoomPlaying.ts
[error] 68-68: Unexpected any. Specify a different type.
(@typescript-eslint/no-explicit-any)
src/api/rooms/getRoomDetail.ts
[error] 45-45: Unexpected any. Specify a different type.
(@typescript-eslint/no-explicit-any)
src/pages/groupDetail/GroupDetail.tsx
[error] 100-100: Unexpected any. Specify a different type.
(@typescript-eslint/no-explicit-any)
src/pages/groupMembers/GroupMembers.tsx
[error] 45-45: Unexpected any. Specify a different type.
(@typescript-eslint/no-explicit-any)
src/api/rooms/getRoomMembers.ts
[error] 50-50: Unexpected any. Specify a different type.
(@typescript-eslint/no-explicit-any)
src/components/memory/RecordItem/RecordItem.tsx
[error] 272-272: Unexpected any. Specify a different type.
(@typescript-eslint/no-explicit-any)
🔇 Additional comments (5)
src/components/creategroup/RoomInfoSection.tsx (1)
19-20: 구분선(showDivider) 추가 변경은 명확하고 부작용 없어 보입니다시각적 분리 강화를 위한
Section showDivider삽입이 일관되며 다른 섹션들과도 패턴이 맞습니다. 접근성/상호작용에 영향이 없는 수평 레이아웃 변경으로 판단됩니다.Also applies to: 34-35, 50-51
src/pages/post/UpdatePost.tsx (1)
111-112: remainImageUrls 전송 방식—백엔드 계약과 일치함 확인됨검토 결과,
UpdateFeedBody인터페이스에remainImageUrls?: string[];로 optional 정의되어 있고, API 샘플 코드에서도
remainImageUrls: ["..."]→ 기존 이미지 일부 유지remainImageUrls: []→ 기존 이미지 전부 삭제- 필드 미포함(undefined) → 기존 이미지 유지
로 처리되고 있습니다. (
apiClient.patch기반 PATCH 요청은 필드 미포함 시 해당 속성을 변경하지 않음)또한 코드 내 모든
remainImageUrls참조(상태 선언·전송·컴포넌트 prop)는 스펙과 정확히 일치하며 오타도 없습니다.따라서 “이미지 없을 때 빈 배열 전송 → 삭제 처리” 로직은 백엔드 계약에 부합하므로 추가 변경 없이 그대로 진행하셔도 좋습니다.
src/components/memory/RecordItem/RecordItem.tsx (3)
292-301: deps에 type 추가: 메뉴 옵션 반영을 위한 올바른 수정
handleClick에서type을 deps에 포함시킨 변경으로, 레코드 타입이 바뀔 때 onPin 포함 여부가 즉시 반영됩니다. 의도에 부합하는 좋은 수정입니다.
311-318:UserAvatar는 div 기반이므로 img 속성(alt, onError)이 적용되지 않습니다.
해당 컴포넌트는 styled.div로 구현돼 있어 alt 텍스트나 onError 핸들러를 받을 수 없습니다. 접근성을 개선하려면 아래 방안을 고려해 주세요:
- div에
role="img"와aria-label을 추가해 스크린리더에 대체 텍스트를 제공- CSS
background-image대신<img>태그로 전환하고 alt/onError 처리- div에 배경 이미지 폴백 CSS를 적용하거나, 이미지 로드 실패 시 다른 스타일을 적용하는 로직 구현
Likely an incorrect or invalid review comment.
272-286: any 타입 제거 및 메뉴 옵션을 MoreMenuProps로 대체ESLint가 지적한
any를 제거하고,openMoreMenu의 인자 타입인MoreMenuProps를 직접 재사용해 타입 안전성을 확보합니다. 아래와 같이 수정해 주세요.--- a/src/components/memory/RecordItem/RecordItem.tsx +++ b/src/components/memory/RecordItem/RecordItem.tsx @@ 272,286 - const menuOptions: any = { - onEdit: handleEdit, - onDelete: handleDeleteConfirm, - onClose: closePopup, - type: 'post', - isWriter: true, - }; - - // 기록(text)일 때만 핀하기 기능 추가 - if (type === 'text') { - menuOptions.onPin = handlePinConfirm; - } - - openMoreMenu(menuOptions); + // `MoreMenuProps`를 사용해 any 제거 + const baseMenuOptions: MoreMenuProps = { + onEdit: handleEdit, + onDelete: handleDeleteConfirm, + onClose: closePopup, + type: 'post', + isWriter: true, + }; + const menuOptions: MoreMenuProps = + type === 'text' + ? { ...baseMenuOptions, onPin: handlePinConfirm } + : baseMenuOptions; + openMoreMenu(menuOptions);• 파일 상단에
import type { MoreMenuProps } from '@/stores/usePopupStore';추가
•openMoreMenu는 이미MoreMenuProps를 파라미터로 받도록 정의되어 있어 별도의 유도 타입 선언 없이 재사용 가능합니다.
| } catch (error: any) { | ||
| console.error('방 상세 정보 조회 API 오류:', error); | ||
|
|
||
| // 모집기간이 만료된 방인 경우 | ||
| if (error.response?.data?.code === 100004) { | ||
| throw new Error('모집기간이 만료된 방입니다.'); | ||
| } | ||
|
|
||
| // 방 접근 권한이 없는 경우 | ||
| if (error.response?.data?.code === 140011) { | ||
| throw new Error('방 접근 권한이 없습니다.'); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
catch any → Axios 오류 내로잉 + 린트 해결
에러 타입을 unknown으로 바꾸고 Axios 오류일 때만 안전하게 코드 접근하세요. 두 가지 코드(100004/140011) 모두 동일한 패턴으로 처리합니다.
- } catch (error: any) {
+ } catch (error: unknown) {
console.error('방 상세 정보 조회 API 오류:', error);
// 모집기간이 만료된 방인 경우
- if (error.response?.data?.code === 100004) {
+ if (isAxiosError<{ code: number }>(error) && error.response?.data?.code === 100004) {
throw new Error('모집기간이 만료된 방입니다.');
}
// 방 접근 권한이 없는 경우
- if (error.response?.data?.code === 140011) {
+ if (isAxiosError<{ code: number }>(error) && error.response?.data?.code === 140011) {
throw new Error('방 접근 권한이 없습니다.');
}파일 상단 임포트 추가:
import { isAxiosError } from 'axios';🧰 Tools
🪛 ESLint
[error] 45-45: Unexpected any. Specify a different type.
(@typescript-eslint/no-explicit-any)
🤖 Prompt for AI Agents
In src/api/rooms/getRoomDetail.ts around lines 45 to 56, change the catch to
accept error as unknown and only treat it as an Axios error after checking with
isAxiosError(error); add the import "isAxiosError" from axios at the file top,
then inside the catch guard with if (isAxiosError(error)) and safely read
error.response?.data?.code to handle codes 100004 and 140011 using the same
pattern; for non-Axios errors rethrow or wrap appropriately to preserve typing
and satisfy the linter.
| } catch (error: any) { | ||
| console.error('독서메이트 조회 API 오류:', error); | ||
|
|
||
| // 방 접근 권한이 없는 경우 | ||
| if (error.response?.data?.code === 140011) { | ||
| throw new Error('방 접근 권한이 없습니다.'); | ||
| } | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
catch 파라미터 any 사용으로 ESLint 위반 → Axios 오류 내로잉으로 해결 제안
현재 catch (error: any)는 @typescript-eslint/no-explicit-any에 걸립니다. unknown으로 전환하고 Axios 오류일 때만 안전하게 코드 접근하도록 내로잉하면 린트 통과와 타입 안전성을 동시에 확보할 수 있습니다.
다음 변경을 제안합니다(해당 범위 내 변경):
- } catch (error: any) {
+ } catch (error: unknown) {
console.error('독서메이트 조회 API 오류:', error);
// 방 접근 권한이 없는 경우
- if (error.response?.data?.code === 140011) {
+ if (isAxiosError<{ code: number }>(error) && error.response?.data?.code === 140011) {
throw new Error('방 접근 권한이 없습니다.');
}
throw error;
}추가로, 파일 상단(임포트 섹션)에 다음을 추가해 주세요:
import { isAxiosError } from 'axios';🧰 Tools
🪛 ESLint
[error] 50-50: Unexpected any. Specify a different type.
(@typescript-eslint/no-explicit-any)
🤖 Prompt for AI Agents
In src/api/rooms/getRoomMembers.ts around lines 50 to 57, replace the catch
parameter typed as any with unknown and narrow it using Axios's isAxiosError;
add "import { isAxiosError } from 'axios';" to the file imports, then inside the
catch check if isAxiosError(error) before accessing error.response?.data?.code
and handle the specific 140011 code as before, otherwise rethrow or throw a
generic error to preserve type-safety and satisfy ESLint.
| } catch (error: any) { | ||
| console.error('진행중인 방 상세 정보 조회 API 오류:', error); | ||
|
|
||
| // 방 접근 권한이 없는 경우 | ||
| if (error.response?.data?.code === 140011) { | ||
| throw new Error('방 접근 권한이 없습니다.'); | ||
| } | ||
|
|
||
| throw error; |
There was a problem hiding this comment.
catch (error: any)로 인한 ESLint 오류 해결 및 Axios 에러 가드 추가
ESLint(@typescript-eslint/no-explicit-any) 에러가 발생합니다. catch 파라미터의 any 제거와 Axios 에러 가드를 추가해 타입 안전성을 확보하세요. 또한 콘솔 로그는 유지하되, 권한 에러만 조건 분기합니다.
- } catch (error: any) {
- console.error('진행중인 방 상세 정보 조회 API 오류:', error);
-
- // 방 접근 권한이 없는 경우
- if (error.response?.data?.code === 140011) {
- throw new Error('방 접근 권한이 없습니다.');
- }
-
- throw error;
- }
+ } catch (error) {
+ // Axios 에러인 경우에만 코드 분기
+ if (isAxiosError(error) && error.response?.data?.code === 140011) {
+ throw new Error('방 접근 권한이 없습니다.');
+ }
+ console.error('진행중인 방 상세 정보 조회 API 오류:', error);
+ throw error;
+ }또한 상단에 아래 유틸 임포트를 추가해주세요:
import { isAxiosError } from 'axios';🧰 Tools
🪛 ESLint
[error] 68-68: Unexpected any. Specify a different type.
(@typescript-eslint/no-explicit-any)
🤖 Prompt for AI Agents
In src/api/rooms/getRoomPlaying.ts around lines 68 to 76, replace the
catch(error: any) with a typed catch (error: unknown) and add an Axios error
guard using isAxiosError: keep the console.error line but only check
error.response?.data?.code === 140011 inside an if (isAxiosError(error) &&
error.response?.data) branch and throw the specific '방 접근 권한이 없습니다.' Error
there; otherwise rethrow the original error (preserving the caught value). Also
add the import "import { isAxiosError } from 'axios';" at the top of the file.
| cursor: pointer; | ||
| `; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
섹션 전체 클릭 전환 OK — 키보드 사용자용 포커스 표시 추가 필요
section을 인터랙티브하게 사용했으므로 키보드 포커스가 명확히 보여야 합니다. 다음처럼 :focus-visible 스타일을 추가해 접근성을 보강해 주세요.
export const CommentSection = styled.section`
display: flex;
flex-direction: column;
width: calc(100% - 40px);
gap: 12px;
background: ${colors.darkgrey.dark};
margin: 20px 20px 0 20px;
padding: 16px 12px;
border-radius: 12px;
- cursor: pointer;
+ cursor: pointer;
+ &:focus-visible {
+ outline: 2px solid ${colors.purple.main};
+ outline-offset: 2px;
+ }
`;📝 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.
| cursor: pointer; | |
| `; | |
| export const CommentSection = styled.section` | |
| display: flex; | |
| flex-direction: column; | |
| width: calc(100% - 40px); | |
| gap: 12px; | |
| background: ${colors.darkgrey.dark}; | |
| margin: 20px 20px 0 20px; | |
| padding: 16px 12px; | |
| border-radius: 12px; | |
| cursor: pointer; | |
| &:focus-visible { | |
| outline: 2px solid ${colors.purple.main}; | |
| outline-offset: 2px; | |
| } | |
| `; |
🤖 Prompt for AI Agents
In src/components/group/CommentSection.styled.ts around lines 13-14, the
interactive section currently only sets cursor: pointer but lacks keyboard focus
styling; add a :focus-visible rule to the section that provides a clear visible
focus indicator (for example an outline or box-shadow with appropriate
outline-offset and accessible color) and ensure the underlying element is
focusable in the DOM (e.g., the component using this styled section should set
tabindex="0" if it isn't naturally focusable) so keyboard users will see the
focus state.
| <StyledCommentSection onClick={onClick}> | ||
| <CommentSectionHeader> |
There was a problem hiding this comment.
비-버튼 요소 클릭: 키보드 접근성과 스크린 리더 지원 추가 필요
section 클릭으로 바뀌면서 탭 포커스/Enter/Space 조작이 불가합니다. role="button", tabIndex, onKeyDown을 추가해 주세요.
- <StyledCommentSection onClick={onClick}>
- <CommentSectionHeader>
+ <StyledCommentSection
+ role="button"
+ tabIndex={0}
+ onClick={onClick}
+ onKeyDown={handleKeyDown}
+ aria-label="오늘의 한마디 이동"
+ >
+ <CommentSectionHeader>컴포넌트 내부에 핸들러를 추가하세요(타입 임포트가 번거로우면 any 생략 가능).
// return 위에 추가
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
onClick();
}
};필요 시 타입 전용 임포트:
import type { KeyboardEvent as ReactKeyboardEvent } from 'react';
// ...
const handleKeyDown = (e: ReactKeyboardEvent) => { /* 동일 */ };🤖 Prompt for AI Agents
In src/components/group/CommentSection.tsx around lines 18-19, the section
element lost keyboard accessibility when changed from a button; add
role="button" and tabIndex={0} to make it focusable, implement an internal
handleKeyDown that prevents default and calls onClick when e.key is "Enter" or "
" (Space), and attach it as onKeyDown={handleKeyDown}; keep onClick as-is. If
you prefer stricter typing, add a type-only import for React.KeyboardEvent and
type the handler, otherwise omit types for brevity.
| cursor: pointer; | ||
| `; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
섹션 전체 클릭 전환 OK — 포커스 가시성 추가 제안
section을 버튼처럼 사용하므로 키보드 포커스 링을 제공해야 합니다.
export const RecordSection = styled.section`
display: flex;
flex-direction: column;
width: calc(100% - 40px);
gap: 12px;
background: ${colors.darkgrey.dark};
margin: 20px 20px 0 20px;
padding: 16px 12px;
border-radius: 12px;
- cursor: pointer;
+ cursor: pointer;
+ &:focus-visible {
+ outline: 2px solid ${colors.purple.main};
+ outline-offset: 2px;
+ }
`;📝 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.
| cursor: pointer; | |
| `; | |
| export const RecordSection = styled.section` | |
| display: flex; | |
| flex-direction: column; | |
| width: calc(100% - 40px); | |
| gap: 12px; | |
| background: ${colors.darkgrey.dark}; | |
| margin: 20px 20px 0 20px; | |
| padding: 16px 12px; | |
| border-radius: 12px; | |
| cursor: pointer; | |
| &:focus-visible { | |
| outline: 2px solid ${colors.purple.main}; | |
| outline-offset: 2px; | |
| } | |
| `; |
🤖 Prompt for AI Agents
In src/components/group/RecordSection.styled.ts around lines 13-14, the section
is styled as a clickable element but lacks keyboard focus visibility; make the
section keyboard-focusable (e.g., ensure it gets tabindex when used as a button)
and add focus styles using :focus and preferably :focus-visible to show a clear
outline or box-shadow (contrasting color and at least 2px) and preserve existing
hover/click styles so keyboard users see the same interactive affordance.
| <StyledRecordSection onClick={onClick}> | ||
| <RecordSectionHeader> |
There was a problem hiding this comment.
컨테이너 onClick 이동은 타당 — 키보드 조작 가능하도록 역할/핸들러 추가 필요
접근성 준수를 위해 role="button", tabIndex, onKeyDown을 추가해 주세요.
- <StyledRecordSection onClick={onClick}>
- <RecordSectionHeader>
+ <StyledRecordSection
+ role="button"
+ tabIndex={0}
+ onClick={onClick}
+ onKeyDown={handleKeyDown}
+ aria-label="기록장 이동"
+ >
+ <RecordSectionHeader>컴포넌트 내부에 핸들러 추가:
// return 위에 추가
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
onClick();
}
};🤖 Prompt for AI Agents
In src/components/group/RecordSection.tsx around lines 23 to 24, the container
has an onClick but lacks keyboard accessibility attributes; add role="button"
and tabIndex={0} to the StyledRecordSection and implement an onKeyDown handler
that calls onClick when Enter or Space is pressed (preventDefault first). Define
the handleKeyDown function above the return to check for e.key === 'Enter' ||
e.key === ' ' (or 'Spacebar' for legacy) and call onClick(); attach it as
onKeyDown={handleKeyDown}. Ensure onClick is invoked safely (check it exists)
and keep the existing onClick prop.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (2)
src/pages/groupMembers/GroupMembers.tsx (1)
45-47: catch 파라미터 unknown 전환 + 안전 가드 도입이 잘 반영되었습니다
catch (err: unknown)로 타입을 좁히고 콘솔 로깅으로 상황을 남기는 접근, 이전 리뷰 피드백과 일치합니다. 👍src/components/group/CommentSection.styled.ts (1)
13-18: section을 버튼처럼 사용할 때 role/tabIndex/키보드 활성화를 보장하세요
section은 기본적으로 포커스/액션 시맨틱이 없습니다. 스타일만으로는 충분치 않으니 TSX에서 다음을 확인해 주세요:tabIndex={0},role="button", Enter/Space로 트리거되는onKeyDown. 확장/접힘 UI라면aria-expanded/aria-controls도 함께 지정하세요.예시(외부 TSX, 참조용):
<StyledCommentSection role="button" tabIndex={0} aria-expanded={isOpen} // 적용 UI에 한해 aria-controls="comment-panel" // 적용 UI에 한해 onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onClick?.(e); } }} onClick={onClick} > ... </StyledCommentSection>
🧹 Nitpick comments (5)
src/pages/groupMembers/GroupMembers.tsx (3)
37-37: parseInt 기수(radix) 명시암묵적 10진 파싱에 의존하지 말고 기수를 명시하는 편이 안전합니다.
- const response: RoomMembersResponse = await getRoomMembers(parseInt(currentRoomId)); + const response: RoomMembersResponse = await getRoomMembers(parseInt(currentRoomId, 10));
29-33: 도달 불가한 null 체크 정리
const currentRoomId = roomId || localStorage.getItem('currentRoomId') || '1';때문에currentRoomId는 항상 truthy이므로 아래if (!currentRoomId)블록은 실행되지 않습니다. 불필요한 분기를 제거해 가독성을 높일 수 있습니다.- if (!currentRoomId) { - setError('방 ID를 찾을 수 없습니다.'); - setLoading(false); - return; - }
48-52: 문자열 비교 기반 권한 에러 처리 통합 가드 함수 도입 권장현재 코드베이스 전반에 걸쳐
'방 접근 권한이 없습니다.'문자열 비교 방식으로 에러를 감지하고 있어, 이후 메시지 변경 시 일일이 수동 수정이 필요합니다. 아래와 같이 공용 유틸(예:src/api/errors.ts)에 접근 거부 가드 함수를 정의하고, 각 컴포넌트에서는 해당 가드만 호출하도록 리팩터링할 것을 권장드립니다.주요 변경 포인트:
src/api/errors.ts에isAccessDeniedError함수 추가- UI/페이지 컴포넌트(
GroupMembers.tsx,GroupDetail.tsx,TodayWords.tsx,RecordWrite.tsx,PollWrite.tsx등)에서 직접 메시지 비교 대신 가드 호출- API 레이어(
getRoomPlaying.ts,getRoomMembers.ts,getRoomDetail.ts)에서는AxiosError.response.data.code === 140011기준으로throw new AccessDeniedError()등 커스텀 에러 타입 사용 검토예시 diff (컴포넌트 측):
- // 방 접근 권한이 없는 경우 - 모임 홈으로 리다이렉트 - if (err instanceof Error && err.message === '방 접근 권한이 없습니다.') { + // 방 접근 권한이 없는 경우 - 모임 홈으로 리다이렉트 + if (isAccessDeniedError(err)) { navigate('/group', { replace: true }); return; }가드 함수 예시 (
src/api/errors.ts):export const ACCESS_DENIED_CODE = 140011; export function isAccessDeniedError(e: unknown): boolean { if (!e || typeof e !== 'object') return false; const err = e as any; // Axios 스타일: response.data.code if (Number(err?.response?.data?.code) === ACCESS_DENIED_CODE) return true; // 도메인 에러 형태: code 필드 if (Number(err?.code) === ACCESS_DENIED_CODE) return true; // 레거시 메시지 비교 (차후 제거) if (e instanceof Error && e.message === '방 접근 권한이 없습니다.') return true; return false; }추가 리팩터링 대상 파일 (주요 경로):
- src/pages/groupMembers/GroupMembers.tsx
- src/pages/groupDetail/{GroupDetail,ParticipatedGroupDetail}.tsx
- src/pages/today-words/TodayWords.tsx
- src/pages/recordwrite/RecordWrite.tsx
- src/pages/pollwrite/PollWrite.tsx
- src/components/memory/{RecordItem,PollRecord}.tsx
- src/api/rooms/{getRoomPlaying,getRoomMembers,getRoomDetail}.ts
위 가드를 활용하면 향후 에러 처리 로직 변경 시 API 레이어만 수정하면 되고, UI는 일관된 로직을 재사용할 수 있어 유지보수성이 크게 향상됩니다.
src/components/group/CommentSection.styled.ts (2)
13-18: 자식 요소 포커스까지 하이라이트하려면 :focus-within 추가 제안내부에 링크/입력 등이 포커스를 받는 경우 컨테이너도 함께 강조되도록 하면 인지성이 좋아집니다.
&:focus-visible { outline: 2px solid ${colors.purple.main}; outline-offset: 2px; } + &:focus-within { + outline: 2px solid ${colors.purple.main}; + outline-offset: 2px; + }
13-18: 호버 힌트/전환 추가로 상호작용성을 더 명확히커서 변경만으로는 부족할 수 있습니다. 가벼운 배경 변화와 트랜지션을 권장합니다. 디자인 톤은 동일 계열 색으로 유지했습니다.
cursor: pointer; &:focus-visible { outline: 2px solid ${colors.purple.main}; outline-offset: 2px; } + transition: background-color 120ms ease, outline-color 120ms ease; + &:hover { + background: ${colors.darkgrey.main}; + }
📜 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 selected for processing (7)
src/api/rooms/getRoomDetail.ts(2 hunks)src/api/rooms/getRoomMembers.ts(2 hunks)src/api/rooms/getRoomPlaying.ts(2 hunks)src/components/group/CommentSection.styled.ts(1 hunks)src/pages/groupDetail/GroupDetail.tsx(1 hunks)src/pages/groupDetail/ParticipatedGroupDetail.tsx(1 hunks)src/pages/groupMembers/GroupMembers.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
- src/pages/groupDetail/ParticipatedGroupDetail.tsx
- src/api/rooms/getRoomMembers.ts
- src/pages/groupDetail/GroupDetail.tsx
- src/api/rooms/getRoomPlaying.ts
- src/api/rooms/getRoomDetail.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/components/group/CommentSection.styled.ts (1)
src/styles/global/global.ts (1)
colors(4-53)
🔇 Additional comments (1)
src/components/group/CommentSection.styled.ts (1)
13-18: 포커스 표시 추가 굿 — 접근성 보강이 반영되었습니다
cursor: pointer와:focus-visible아웃라인/오프셋 추가가 의도와 일치하며 키보드 사용자에게 명확한 포커스 피드백을 제공합니다. 색상 대비(보라색 #6868FF vs 배경 #282828)도 충분히 눈에 띕니다.
#️⃣연관된 이슈
📝작업 내용
스크린샷 (선택)
💬리뷰 요구사항(선택)
Summary by CodeRabbit