Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Walkthrough알림 클릭 시 Changes
Sequence Diagram(s)sequenceDiagram
participant User as 사용자
participant UI as Notice UI
participant API as /notifications/check
participant Router as 앱 라우터
rect #E6F2FF
Note over UI,API: 알림 클릭 흐름 (신규)
User->>UI: 알림 클릭 (notificationId)
UI->>API: POST /notifications/check { notificationId }
API-->>UI: { route, params }
alt route == NONE
UI->>Router: 알림 목록 유지 또는 기본 처리
else route == FEED_USER / FEED_DETAIL / ROOM_*
UI->>Router: navigate(route, params)
end
Router-->>UI: 네비게이션 완료
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro Disabled knowledge base sources:
📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (4)
index.html (2)
2-2: html lang="en" → "ko"로 변경 권장 (SEO/접근성).문서 주언어가 한국어라 lang을 ko로 지정하는 것이 적절합니다.
다음과 같이 수정 권장:
-<html lang="en"> +<html lang="ko">
15-22: viewport 메타 태그가 중복입니다 — 하나로 통일하세요.중복 시 브라우저별 해석 차로 예기치 않은 동작이 날 수 있습니다. 아래처럼 하나만 유지 권장(라인 22 유지, 라인 15 삭제).
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />src/pages/notice/Notice.tsx (2)
167-174: 스크롤 컨테이너 크기 확정 필요 가능성 — flex:1/min-height:0 추가 제안.Wrapper가 overflow:hidden이고 페이지 스크롤을 막는 구조라면, 리스트가 자체 스크롤 되려면 컨테이너에 높이를 확정해주는 편이 안전합니다.
const NotificationList = styled.div` display: flex; flex-direction: column; gap: 12px; padding: 0 20px 20px 20px; width: 100%; + flex: 1; + min-height: 0; /* flex 컨테이너 내 내부 스크롤 보장 */ overflow-y: auto;
61-79: 무한 스크롤 관찰 루트 확인 요청.NotificationList가 내부 스크롤 컨테이너라면 IntersectionObserver의 root를 해당 엘리먼트로 지정해야 트리거가 안정적입니다. 현재 root:null(뷰포트)입니다.
필요 시: NotificationList에 ref 부여 후
new IntersectionObserver(cb, { root: listEl, ... })형태로 전환을 검토해주세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
index.html(1 hunks)src/pages/notice/Notice.tsx(1 hunks)
🔇 Additional comments (2)
index.html (1)
18-18: 메타 description 추가 좋습니다.검색 스니펫 품질 향상에 기여합니다.
src/pages/notice/Notice.tsx (1)
174-179: 브라우저별 스크롤바 숨김 처리 적절합니다.IE/Firefox/WebKit 모두 커버합니다. 키보드 스크롤/스크린리더 사용성도 한번 실제 기기에서 확인 부탁드립니다.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (8)
src/api/notifications/postNotificationsCheck.ts (2)
7-15: route/params를 구체 타입으로 선언해 분기에서 타입 안전성 확보string 대신 리터럴 유니온과 분기별 params 맵으로 모델링하면 Notice.tsx의
as캐스트를 제거할 수 있고, 잘못된 분기/파라미터가 컴파일 타임에 잡힙니다.백엔드 응답 스펙(라우트 키/파라미터 키·타입)을 한 번만 확인해 주세요.
-export interface PostNotificationsCheckResponse<Params = Record<string, unknown>> { - isSuccess: boolean; - code: number; - message: string; - data: { - route: string; // e.g., 'POST_DETAIL' - params?: Params; // e.g., { postId: 123 } - }; -} +export type NotificationRoute = + | 'NONE' + | 'FEED_USER' + | 'FEED_DETAIL' + | 'ROOM_MAIN' + | 'ROOM_DETAIL' + | 'ROOM_POST_DETAIL' + | 'ROOM_RECORD_DETAIL' + | 'ROOM_VOTE_DETAIL'; + +export type RouteParamsMap = { + NONE: Record<string, never>; + FEED_USER: { userId: number }; + FEED_DETAIL: { feedId: number }; + ROOM_MAIN: { roomId: number }; + ROOM_DETAIL: { roomId: number }; + ROOM_POST_DETAIL: { roomId: number; postId?: number; page?: number; postType?: 'RECORD' | 'VOTE' }; + ROOM_RECORD_DETAIL: { roomId: number; postId?: number; page?: number; postType: 'RECORD' }; + ROOM_VOTE_DETAIL: { roomId: number; postId?: number; page?: number; postType: 'VOTE' }; +}; + +export interface PostNotificationsCheckResponse { + isSuccess: boolean; + code: number; + message: string; + data: + | { route: 'NONE' } + | { route: 'FEED_USER'; params: RouteParamsMap['FEED_USER'] } + | { route: 'FEED_DETAIL'; params: RouteParamsMap['FEED_DETAIL'] } + | { route: 'ROOM_MAIN'; params: RouteParamsMap['ROOM_MAIN'] } + | { route: 'ROOM_DETAIL'; params: RouteParamsMap['ROOM_DETAIL'] } + | { route: 'ROOM_POST_DETAIL'; params: RouteParamsMap['ROOM_POST_DETAIL'] } + | { route: 'ROOM_RECORD_DETAIL'; params: RouteParamsMap['ROOM_RECORD_DETAIL'] } + | { route: 'ROOM_VOTE_DETAIL'; params: RouteParamsMap['ROOM_VOTE_DETAIL'] }; +}
18-25: 반환 타입을 명시해 사용처 추론 안정화함수 반환 타입을 명시하면 사용처에서
res.data의 타입이 안정적으로 추론됩니다.-export const postNotificationsCheck = async (notificationId: number) => { +export const postNotificationsCheck = async ( + notificationId: number, +): Promise<PostNotificationsCheckResponse> => { const body: PostNotificationsCheckRequest = { notificationId }; const response = await apiClient.post<PostNotificationsCheckResponse>( '/notifications/check', body, ); return response.data; };src/pages/notice/Notice.tsx (6)
8-8: 타입도 함께 가져와as단언 제거 준비API 응답 타입(예: PostNotificationsCheckResponse/NotificationRoute)을 함께 import하면 아래
res.data as { ... }단언을 없애고 분기별 파라미터를 타입 안전하게 처리할 수 있습니다. (API 타입 정비가 선행되면 적용)
86-97: 이미 읽은 알림 중복 호출 방지 + 낙관적 읽음 처리 활성화
- 이미 읽은 항목은 API 재호출을 막아 불필요한 트래픽/레이스 방지
- 즉시 읽음 UI 반영(낙관적 업데이트)으로 체감 반응성 개선
-const handleNotificationClick = async (notif: NotificationItem) => { +const handleNotificationClick = async (notif: NotificationItem) => { + if (notif.isChecked) return; // 이미 읽은 알림은 무시 try { - const res = await postNotificationsCheck(notif.notificationId); + // UI 즉시 반영: 읽음 처리(낙관적) + setNotifications(prev => + prev.map(item => + item.notificationId === notif.notificationId ? { ...item, isChecked: true } : item, + ), + ); + const res = await postNotificationsCheck(notif.notificationId); if (!res.isSuccess) return; - // UI 즉시 반영: 읽음 처리 - // setNotifications(prev => - // prev.map(item => - // item.notificationId === notif.notificationId ? { ...item, isChecked: true } : item, - // ), - // ); + // 성공 시 그대로 유지
98-156: 서버 params를 숫자로 안전 변환 후 검증
as number는 런타임 변환이 없어 '123' 같은 문자열이 그대로 통과합니다. Number() 변환 + isFinite 검증으로 방어하세요. 또한 알 수 없는 route는 경고 로그를 남기면 추적이 쉽습니다.- const { route, params } = res.data as { route: string; params?: Record<string, unknown> }; + const { route, params } = res.data as { route: string; params?: Record<string, unknown> }; // 서버 라우팅 키 → 실제 앱 경로 매핑 switch (route) { @@ - case 'FEED_USER': { - const userId = (params?.userId as number) ?? undefined; - if (userId !== undefined) { - navigate(`/otherfeed/${userId}`); - } + case 'FEED_USER': { + const userId = Number(params?.userId); + if (Number.isFinite(userId)) { + navigate(`/otherfeed/${userId}`); + } else { + console.warn('알림 이동 실패: 잘못된 userId', params?.userId); + } break; } @@ - case 'FEED_DETAIL': { - const feedId = (params?.feedId as number) ?? undefined; - if (feedId !== undefined) { - navigate(`/feed/${feedId}`); - } + case 'FEED_DETAIL': { + const feedId = Number(params?.feedId); + if (Number.isFinite(feedId)) { + navigate(`/feed/${feedId}`); + } else { + console.warn('알림 이동 실패: 잘못된 feedId', params?.feedId); + } break; } @@ - case 'ROOM_MAIN': { - const roomId = (params?.roomId as number) ?? undefined; - if (roomId !== undefined) navigate(`/group/detail/joined/${roomId}`); + case 'ROOM_MAIN': { + const roomId = Number(params?.roomId); + if (Number.isFinite(roomId)) navigate(`/group/detail/joined/${roomId}`); + else console.warn('알림 이동 실패: 잘못된 roomId', params?.roomId); break; } @@ - case 'ROOM_DETAIL': { - const roomId = (params?.roomId as number) ?? undefined; - if (roomId !== undefined) navigate(`/group/detail/${roomId}`); + case 'ROOM_DETAIL': { + const roomId = Number(params?.roomId); + if (Number.isFinite(roomId)) navigate(`/group/detail/${roomId}`); + else console.warn('알림 이동 실패: 잘못된 roomId', params?.roomId); break; } @@ - case 'ROOM_POST_DETAIL': - case 'ROOM_RECORD_DETAIL': - case 'ROOM_VOTE_DETAIL': { - const roomId = (params?.roomId as number) ?? undefined; - const postId = (params?.postId as number) ?? undefined; - const page = (params?.page as number) ?? undefined; - const postType = params?.postType as 'RECORD' | 'VOTE'; - if (roomId !== undefined) { - navigate(`/rooms/${roomId}/memory`, { - state: { focusPostId: postId, postType, page }, - }); - } + case 'ROOM_POST_DETAIL': + case 'ROOM_RECORD_DETAIL': + case 'ROOM_VOTE_DETAIL': { + const roomId = Number(params?.roomId); + const postId = params?.postId != null ? Number(params?.postId) : undefined; + const page = params?.page != null ? Number(params?.page) : undefined; + const postType = params?.postType as 'RECORD' | 'VOTE' | undefined; + if (Number.isFinite(roomId)) { + navigate(`/rooms/${roomId}/memory`, { state: { focusPostId: postId, postType, page } }); + } else { + console.warn('알림 이동 실패: 잘못된 roomId', params?.roomId); + } break; } @@ - default: - break; + default: + console.warn('알 수 없는 알림 route:', route, params); + break; }추가 확인:
- 'ROOM_POST_DETAIL'에서 postType이 'POST' 등 다른 값일 수 있으면 상위 API 타입/분기 정의도 함께 조정이 필요합니다. 백엔드 스펙을 확인해 주세요.
157-160: API 실패 시 낙관적 읽음 처리 롤백성공 시점 이전에 읽음 표시했으므로, 실패하면 되돌려야 합니다.
- } catch (e) { - // noop: 실패 시 네비게이션 없이 무시 - console.error('알림 확인 처리 실패:', e); - } + } catch (e) { + // 실패 시 읽음 처리 롤백 + setNotifications(prev => + prev.map(item => + item.notificationId === notif.notificationId ? { ...item, isChecked: false } : item, + ), + ); + console.error('알림 확인 처리 실패:', e); + }
187-187: onClick 바인딩 OK아이템별 클릭 처리 연결이 명확합니다. 위 가드/낙관적 업데이트 적용 시 UX가 더 좋아집니다.
246-251: 스크롤바 숨김은 접근성에 영향—대체 신호 제공 검토시각적 스크롤 표시가 사라지면 스크롤 가능성을 인지하기 어렵습니다. 그라데이션 페이드, “더 보기” 힌트, 키보드 포커스 이동 지원(tabIndex/role="region" aria-label) 등 보완책을 검토해 주세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
src/api/notifications/postNotificationsCheck.ts(1 hunks)src/pages/notice/Notice.tsx(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/pages/notice/Notice.tsx (2)
src/api/notifications/getNotifications.ts (1)
NotificationItem(3-10)src/api/notifications/postNotificationsCheck.ts (1)
postNotificationsCheck(18-25)
src/api/notifications/postNotificationsCheck.ts (1)
src/api/index.ts (1)
apiClient(7-13)
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
src/pages/memory/Memory.tsx (3)
35-46: voteItems 미존재 시 런타임 에러 가능 + O(n^2) 연산 — 안전 가드 및 1-pass로 개선 제안
- postType이 RECORD인데 voteItems가 undefined/null이면 map에서 크래시 납니다.
- maxCount를 매 아이템마다 계산해 O(n^2)입니다. 한 번만 계산하도록 바꿔주세요.
아래처럼 postType 가드 + items 축약 + reduce 1-pass로 개선을 제안드립니다.
- pollOptions: post.voteItems.map(item => { - const maxCount = Math.max(...post.voteItems.map(v => v.count || 0)); - return { - id: item.voteItemId.toString(), - text: item.itemName, - percentage: item.percentage, - count: item.count || 0, - isHighest: (item.count || 0) === maxCount && maxCount > 0, - voteItemId: item.voteItemId, - isVoted: item.isVoted, - }; - }), + pollOptions: post.postType === 'VOTE' + ? (() => { + const items = post.voteItems ?? []; + const maxCount = items.reduce((m, v) => Math.max(m, v.count ?? 0), 0); + return items.map((item) => ({ + id: String(item.voteItemId), + text: item.itemName, + percentage: item.percentage, + count: item.count ?? 0, + isHighest: (item.count ?? 0) === maxCount && maxCount > 0, + voteItemId: item.voteItemId, + isVoted: item.isVoted, + })); + })() + : [],
54-54: Zustand selector로 함수만 구독하여 불필요 리렌더 방지스토어 전체가 아닌 액션만 구독하면 다른 상태 변경 시 이 컴포넌트 리렌더를 피할 수 있습니다.
- const { openCommentBottomSheet } = useCommentBottomSheetStore(); + const openCommentBottomSheet = useCommentBottomSheetStore(s => s.openCommentBottomSheet);
156-179: 라우팅 state 처리 useEffect 의존성/가드 보강 및 ESLint 주석 제거
- focusPostId/page는 truthy 체크 대신 number 체크로 안전하게 처리하세요.
- navigate, openCommentBottomSheet, pathname 등을 deps에 포함해 ESLint 주석을 제거할 수 있습니다.
- // Notice에서 넘어온 state(page, focusPostId 등)로 초기 필터 적용 - useEffect(() => { - type MemoryLocationState = { - page?: number; - focusPostId?: number; - postType?: 'RECORD' | 'VOTE'; - openComments?: boolean; - } | null; - const state = (location.state as MemoryLocationState) || null; - const initialPage = state?.page; - if (initialPage && !selectedPageRange) { - setSelectedPageRange({ start: initialPage, end: initialPage }); - setActiveFilter('page'); - } - - // 댓글 모달 자동 오픈 처리 - if (state?.openComments && state.focusPostId && state.postType) { - openCommentBottomSheet(state.focusPostId, state.postType); - // 동일 경로 재진입 시 중복 오픈 방지를 위해 state 제거 - navigate(location.pathname, { replace: true }); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [location.state, roomId]); + // Notice에서 넘어온 state(page, focusPostId 등)로 초기 필터/댓글 처리 + useEffect(() => { + type MemoryLocationState = { + page?: number; + focusPostId?: number; + postType?: 'RECORD' | 'VOTE'; + openComments?: boolean; + } | null; + const state = (location.state as MemoryLocationState) || null; + const initialPage = state?.page; + if (typeof initialPage === 'number' && !selectedPageRange) { + setSelectedPageRange({ start: initialPage, end: initialPage }); + setActiveFilter('page'); + } + + // 댓글 모달 자동 오픈 처리 + if (state?.openComments && typeof state.focusPostId === 'number' && state.postType) { + openCommentBottomSheet(state.focusPostId, state.postType); + // 동일 경로 재진입 시 중복 오픈 방지를 위해 state 제거 + navigate(location.pathname, { replace: true }); + } + }, [location.state, location.pathname, roomId, selectedPageRange, openCommentBottomSheet, navigate]);추가로,
MemoryLocationState타입은 파일 상단에 선언해 재사용/가독성을 높이는 것을 권장합니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
src/pages/memory/Memory.tsx(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/pages/memory/Memory.tsx (1)
src/stores/useCommentBottomSheetStore.ts (1)
useCommentBottomSheetStore(14-26)
🔇 Additional comments (1)
src/pages/memory/Memory.tsx (1)
9-9: 알림 댓글 바텀시트 스토어 의존성 추가 — LGTM주입 방향 자연스럽고, 파일 경로 별칭도 일관됩니다.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
src/pages/notice/Notice.tsx (4)
86-165: as number 캐스트는 런타임에서 무효 — params를 안전하게 파싱/검증하세요서버에서 문자열("123")이나 예기치 않은 타입이 올 경우에도
as number는 런타임에서 아무 일도 하지 않아 잘못된 값이 경로에 삽입될 수 있습니다. 숫자/유니온을 런타임에서 안전하게 파싱/검증하도록 보완을 권장합니다.아래처럼 간단한 파서(helper)를 추가하고 각 분기에서 사용해 주세요:
- const { route, params } = res.data as { route: string; params?: Record<string, unknown> }; + const { route, params } = res.data as { route: string; params?: Record<string, unknown> }; + const toNum = (v: unknown) => { + const n = Number(v); + return Number.isFinite(n) ? n : undefined; + }; + const toPostType = (v: unknown) => + v === 'RECORD' || v === 'VOTE' ? (v as 'RECORD' | 'VOTE') : undefined; @@ - case 'FEED_USER': { - const userId = (params?.userId as number) ?? undefined; + case 'FEED_USER': { + const userId = toNum(params?.userId); if (userId !== undefined) { navigate(`/otherfeed/${userId}`); } break; } @@ - case 'FEED_DETAIL': { - const feedId = (params?.feedId as number) ?? undefined; + case 'FEED_DETAIL': { + const feedId = toNum(params?.feedId); if (feedId !== undefined) { navigate(`/feed/${feedId}`); } break; } @@ - case 'ROOM_MAIN': { - const roomId = (params?.roomId as number) ?? undefined; + case 'ROOM_MAIN': { + const roomId = toNum(params?.roomId); if (roomId !== undefined) navigate(`/group/detail/joined/${roomId}`); break; } @@ - case 'ROOM_DETAIL': { - const roomId = (params?.roomId as number) ?? undefined; + case 'ROOM_DETAIL': { + const roomId = toNum(params?.roomId); if (roomId !== undefined) navigate(`/group/detail/${roomId}`); break; } @@ - case 'ROOM_POST_DETAIL': { - const roomId = (params?.roomId as number) ?? undefined; - const postId = (params?.postId as number) ?? undefined; - const page = (params?.page as number) ?? undefined; - const postType = params?.postType as 'RECORD' | 'VOTE'; - const shouldOpenComments = (params as { openComments?: boolean })?.openComments === true; + case 'ROOM_POST_DETAIL': { + const roomId = toNum(params?.roomId); + const postId = toNum(params?.postId); + const page = toNum(params?.page); + const postType = toPostType(params?.postType); + const shouldOpenComments = + (params as { openComments?: unknown })?.openComments === true; if (roomId !== undefined) { navigate(`/rooms/${roomId}/memory`, { state: { focusPostId: postId, postType, page, ...(shouldOpenComments ? { openComments: true } : {}), }, }); } break; }추가로, 중복 클릭 방지(연속 탭 시 다중 호출)용으로
processingIdref를 사용해 가드하는 것도 추천드립니다.
91-97: 읽음 상태 UI 반영이 주석 처리됨 — 성공 후 바로 반영하거나 낙관적 업데이트로 전환하세요현재 UX상 클릭 후 읽음 처리 표시가 필요해 보입니다. 최소한 API 성공 시 즉시 UI를 갱신해 주세요.
- // UI 즉시 반영: 읽음 처리 - // setNotifications(prev => - // prev.map(item => - // item.notificationId === notif.notificationId ? { ...item, isChecked: true } : item, - // ), - // ); + // UI 반영: 읽음 처리 (성공 후 업데이트) + setNotifications(prev => + prev.map(item => + item.notificationId === notif.notificationId ? { ...item, isChecked: true } : item, + ), + );원하시면 낙관적 업데이트(+실패 시 롤백)로 바꾸는 코드도 제공하겠습니다.
250-255: 스크롤바 숨김 처리 LGTM — 모바일 모멘텀/오버스크롤 보완 제안iOS/안드로이드 경험 개선을 위해 모멘텀 스크롤과 오버스크롤 제어를 추가하는 것을 권장합니다.
/* Hide scrollbar but keep scroll */ -ms-overflow-style: none; /* IE and Edge */ scrollbar-width: none; /* Firefox */ &::-webkit-scrollbar { display: none; /* Chrome, Safari, Opera */ } + /* Mobile scrolling polish */ + -webkit-overflow-scrolling: touch; + overscroll-behavior-y: contain;
158-164: 알 수 없는 route/실패 케이스에 대한 사용자 피드백·관측성 보강default 분기와 catch에서 단순 무시/console.error만 하면 사용자 혼란이 생길 수 있습니다. 토스트 안내와 모니터링(Sentry 등) 연동을 검토해 주세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
src/pages/notice/Notice.tsx(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/pages/notice/Notice.tsx (2)
src/api/notifications/getNotifications.ts (1)
NotificationItem(3-10)src/api/notifications/postNotificationsCheck.ts (1)
postNotificationsCheck(18-25)
🔇 Additional comments (1)
src/pages/notice/Notice.tsx (1)
8-8: 새 API 래퍼 import LGTM의존성/경로 문제 없어 보입니다.
| <NotificationCard | ||
| key={notif.notificationId ?? idx} | ||
| read={notif.isChecked} | ||
| // onClick={() => handleReadNotification(idx)} | ||
| onClick={() => handleNotificationClick(notif)} | ||
| > | ||
| {!notif.isChecked && <UnreadDot />} |
There was a problem hiding this comment.
클릭 가능한 div의 키보드 접근성 부족 — role/tabIndex/onKeyDown 추가 필요
현재 마우스 클릭만 가능하고 키보드로는 활성화할 수 없습니다. 최소한 role/button 시맨틱과 Enter/Space 처리 추가가 필요합니다.
- <NotificationCard
+ <NotificationCard
key={notif.notificationId ?? idx}
read={notif.isChecked}
- onClick={() => handleNotificationClick(notif)}
+ onClick={() => handleNotificationClick(notif)}
+ role="button"
+ tabIndex={0}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ handleNotificationClick(notif);
+ }
+ }}
>📝 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.
| <NotificationCard | |
| key={notif.notificationId ?? idx} | |
| read={notif.isChecked} | |
| // onClick={() => handleReadNotification(idx)} | |
| onClick={() => handleNotificationClick(notif)} | |
| > | |
| {!notif.isChecked && <UnreadDot />} | |
| <NotificationCard | |
| key={notif.notificationId ?? idx} | |
| read={notif.isChecked} | |
| onClick={() => handleNotificationClick(notif)} | |
| role="button" | |
| tabIndex={0} | |
| onKeyDown={(e) => { | |
| if (e.key === 'Enter' || e.key === ' ') { | |
| e.preventDefault(); | |
| handleNotificationClick(notif); | |
| } | |
| }} | |
| > | |
| {!notif.isChecked && <UnreadDot />} |
🤖 Prompt for AI Agents
In src/pages/notice/Notice.tsx around lines 188-193, the NotificationCard is not
keyboard-accessible; add role="button", tabIndex={0}, and an onKeyDown handler
that maps Enter and Space keys to the same action as the onClick (i.e., call
handleNotificationClick(notif)), and preventDefault for Space to avoid page
scroll; ensure the onKeyDown uses the same notif variable and preserves existing
onClick behavior.
| // 피드 2~6번 (피드상세페이지로 이동) | ||
| case 'FEED_DETAIL': { | ||
| const feedId = (params?.feedId as number) ?? undefined; | ||
| if (feedId !== undefined) { | ||
| navigate(`/feed/${feedId}`); | ||
| } | ||
| break; | ||
| } |
There was a problem hiding this comment.
예 맞습니다 별거없습니다 ㅋㅋ
src/pages/notice/Notice.tsx
Outdated
| case 'ROOM_POST_DETAIL': | ||
| case 'ROOM_RECORD_DETAIL': | ||
| case 'ROOM_VOTE_DETAIL': { | ||
| case 'ROOM_POST_DETAIL': { |
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (3)
package.json(1 hunks)src/lib/ga.ts(2 hunks)src/main.tsx(1 hunks)
🔇 Additional comments (2)
package.json (1)
25-25: react-ga4 의존성 추가 확인 완료GA 초기화 로직과 연동되는 필수 의존성이며 다른 충돌 요소도 없어 보입니다.
src/main.tsx (1)
4-6: 초기 페이지뷰 전송 여부 확인 필요Line 4에서 기존
sendPageView(...)호출이 제거되었는데,initGA()내부에서 최초 페이지뷰 전송까지 보장되는지 점검 부탁드립니다. 누락 시 첫 진입 세션 데이터가 빠질 수 있으니,ReactGA.send({ hitType: 'pageview', ... })등에 해당하는 처리가 포함돼 있는지 확인해 주세요.
#️⃣연관된 이슈
없음
📝작업 내용
구글 검색시 노출되는 메타데이터 추가

알림센터 스크롤 바 보이지 않게 수정

알림센터에서 알림을 누를 때, 해당하는 기록이 필터링된 채로 리다이렉트되게끔 구현 (만약, 댓글&답글에 해당하는 알림의 경우 해당 기록의 댓글 모달까지 열리게끔 함)
2025-09-24.11.54.56.mov
2025-09-25.3.51.04.mov
💬리뷰 요구사항
없음
Summary by CodeRabbit