Conversation
* feat: 전처리 로직 및 WebWorker 구현 * feat: 전처리 적용 및 preview 동시성 제어 로직 추가 * refactor: 리뷰 반영
* hotfix: 하단바 너비 수정 * chore: 불필요한 값 제거 * refactor: 고정 gap 제거
This reverts commit c51ec85.
* chore: asset 추가 * feat: 하단바 리디자인 반영 및 레이아웃 수정
* refactor: 광고 개수 측정 시기 변경 및 기본값 제거 * feat: 스켈레톤 UI 추가
* feat: 알림 API 및 스트림 기반 추가 * feat: 인앱 알림 레이어 추가 * feat: 알림 페이지 및 헤더 진입 구현 * fix: 알림 스트림 401 재시도 조건 정리 * fix: 알림 목록 이동 차단 제거 * refactor: 알림 공용 훅 위치 정리 * fix: 알림 재연결 캐시 동기화 추가 * fix: 알림 목록 토스트 큐 누적 방지 * fix: 알림 읽음 카운트 감소 조건 보강
* chore: pwa용 이미지 제거 * refactor: auth 도메인 쿼리와 뮤테이션 정리 * refactor: council과 schedule 조회 훅 정리 * refactor: chat과 notification 캐시 처리 정리 * refactor: club 조회와 지원 플로우 정리 * refactor: manager 도메인 캐시 처리 정리 * refactor: studyTime 도메인 쿼리와 뮤테이션 정리 * refactor: 광고와 업로드 도메인 훅 정리
* refactor: auth와 user myInfo 훅 정리 * refactor: club과 schedule 조회 훅 정리 * refactor: chat과 notification 훅 구조 정리 * refactor: club 지원 뮤테이션 훅 정리 * refactor: manager 뮤테이션 훅 구조 정리 * refactor: mutation 훅 cache 정리 * refactor: 컨벤션 통일 * refactor: isRead 조건 정리 * fix: 채팅 스크롤 문제 수정 * refactor: 불필요한 코드 제거
* chore: 가공용 safeArea 변수 선언 * refactor: 고정 패딩 값 수정 및 safeArea 적용 변경 * feat: 키보드 활성화 감지 및 safeArea 적용 여부 기능 추가 * refactor: 매직넘버 상수화 및 가로모드 처리
* refactor: 채팅 viewport 훅 네이밍 정리 * refactor: viewport 높이 잠금 훅 적용 시점 조정
* refactor: 채팅 viewport 훅 네이밍 정리 * refactor: viewport 높이 잠금 훅 적용 시점 조정 * fix: 채팅 화면 스크롤 잠금으로 키보드 흔들림 완화
|
Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (8)
Walkthrough라우팅과 레이아웃을 확장하고 React Query 설정을 대규모로 중앙화했습니다. App에 lazy 로딩된 NotificationsPage와 최상위 Possibly related PRs
✨ Finishing Touches🧪 Generate unit tests (beta)
|
* fix: 채팅 화면 상단 고정 깨짐과 빈 공간 잔류 수정 * refactor: 라우트 조건 수정 * fix: 문서 스크롤 위치 감지 보강 * refactor: 입력 요소 판별 유틸과 스크롤 주석 정리
* fix: 채팅 문서 스크롤 제스처 차단 * fix: 입력 요소 터치 동작 예외 처리
* fix: 키보드 활성화 시 채팅 하단 정렬 유지 * refactor: 채팅 리사이즈 관찰 안정화
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/pages/Chat/ChatRoom.tsx (1)
82-84:⚠️ Potential issue | 🟡 Minor
chatRoomIdundefined 처리 필요
useParams의chatRoomId가 undefined일 수 있어Number(chatRoomId)가NaN을 반환할 수 있습니다.🛡️ 제안
function ChatRoom() { const { chatRoomId } = useParams(); + if (!chatRoomId) return null; const { sendMessage, chatMessages, fetchNextPage, hasNextPage, isFetchingNextPage, chatRoomList, isSendingMessage } = useChat(Number(chatRoomId));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Chat/ChatRoom.tsx` around lines 82 - 84, The code currently passes Number(chatRoomId) into useChat which will produce NaN if useParams() returned undefined for chatRoomId; update ChatRoom.tsx to validate and normalize chatRoomId before calling useChat (e.g., check that chatRoomId from useParams() is a defined numeric string, parse it with parseInt or Number only after the check, or provide a safe fallback id or early return UI when chatRoomId is missing). Ensure the change touches the lines using useParams, the chatRoomId variable, and the useChat(...) call so useChat always receives a valid number.
🧹 Nitpick comments (45)
src/apis/inquiry/mutations.ts (1)
2-2: 상대 경로 import 대신@/*별칭을 사용해주세요.
from '.'는 현재 컨벤션과 맞지 않습니다.@/apis/inquiry형태로 변경하는 게 좋습니다.As per coding guidelines
**/*.{ts,tsx}: Use@/*alias for import paths instead of relative paths.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/apis/inquiry/mutations.ts` at line 2, The import in mutations.ts uses a relative path; replace "import { postInquiry } from '.';" with the project alias form so it follows the guideline (use the "@/..." alias); update the import to reference the inquiry module via the alias (e.g., import { postInquiry } from '@/apis/inquiry') making sure to update any other similar imports in the file that reference the inquiry index.src/apis/studyTime/mutations.ts (1)
2-3: 상대 경로 대신 path alias 사용 권장♻️ 수정 제안
-import type { StopTimerRequest } from './entity'; -import { startStudyTimer, stopStudyTimer } from '.'; +import type { StopTimerRequest } from '@/apis/studyTime/entity'; +import { startStudyTimer, stopStudyTimer } from '@/apis/studyTime';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/apis/studyTime/mutations.ts` around lines 2 - 3, The imports in this module use relative paths; replace them with your project path alias to keep imports consistent and resilient—change "import type { StopTimerRequest } from './entity';" to use the configured alias (e.g. import type { StopTimerRequest } from '@/apis/studyTime/entity';) and change "import { startStudyTimer, stopStudyTimer } from '.';" to the alias form that explicitly imports startStudyTimer and stopStudyTimer from the studyTime index (e.g. import { startStudyTimer, stopStudyTimer } from '@/apis/studyTime';) ensuring the symbol names StopTimerRequest, startStudyTimer, and stopStudyTimer remain unchanged.src/apis/studyTime/hooks.ts (1)
3-4: 상대 경로 대신 path alias 사용 권장♻️ 수정 제안
-import { studyTimeMutations } from './mutations'; -import { studyTimeQueryKeys } from './queries'; +import { studyTimeMutations } from '@/apis/studyTime/mutations'; +import { studyTimeQueryKeys } from '@/apis/studyTime/queries';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/apis/studyTime/hooks.ts` around lines 3 - 4, Replace the relative imports for studyTimeMutations and studyTimeQueryKeys in hooks.ts with your project's path aliases (e.g., use `@/`... style aliases) so imports use the configured alias instead of a relative path; update the import lines that bring in studyTimeMutations and studyTimeQueryKeys to the alias form and verify TypeScript/tsconfig paths and build tooling resolve them.src/pages/Timer/hooks/useStudyTimer.ts (1)
2-2: 상대 경로 대신 path alias 사용 권장♻️ 수정 제안
-import { useStudyTime } from './useStudyTime'; +import { useStudyTime } from '@/pages/Timer/hooks/useStudyTime';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Timer/hooks/useStudyTimer.ts` at line 2, In useStudyTimer.ts replace the relative import of the useStudyTime hook (import { useStudyTime } from './useStudyTime') with the project path-alias form your codebase uses (e.g. import from the alias root like `@/`... or src/...) so all imports are consistent with path-alias configuration; update the import statement to reference the alias and ensure your tsconfig/webpack alias entry is respected and tests/build still resolve the module.src/apis/studyTime/queries.ts (1)
2-3: 상대 경로 대신 path alias 사용 권장코딩 가이드라인에 따라
@/*alias를 사용해주세요.♻️ 수정 제안
-import type { StudyRankingParams } from './entity'; -import { getMyStudyTimeRanking, getStudyTimeRanking, getStudyTimeSummary } from '.'; +import type { StudyRankingParams } from '@/apis/studyTime/entity'; +import { getMyStudyTimeRanking, getStudyTimeRanking, getStudyTimeSummary } from '@/apis/studyTime';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/apis/studyTime/queries.ts` around lines 2 - 3, Replace the relative import paths with the project path alias: change the import of StudyRankingParams from './entity' to '@/apis/studyTime/entity' (or the appropriate aliased module) and change the imports of getMyStudyTimeRanking, getStudyTimeRanking, getStudyTimeSummary from '.' to '@/apis/studyTime' (or the aliased entrypoint); update the import statements that reference StudyRankingParams, getMyStudyTimeRanking, getStudyTimeRanking, and getStudyTimeSummary to use the `@/`* alias so they follow the coding guideline.src/pages/Schedule/index.tsx (1)
31-34:useQueryvsuseSuspenseQuery사용 검토코딩 가이드라인에서
useSuspenseQuery사용을 권장하고 있습니다. 현재enabled옵션과 함께useQuery를 사용 중인데, 이 페이지가 route entry이고schedules ?? []폴백이 있어 의도적인 선택으로 보입니다.
useSuspenseQuery로 전환할 경우 Suspense boundary가 필요하며, 초기 로딩 상태 처리 방식이 달라집니다. 현재 구현이 의도된 것이라면 무시해도 됩니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Schedule/index.tsx` around lines 31 - 34, The code uses useQuery({...scheduleQueries.monthly({ year, month }), enabled: Boolean(year && month)}) which the review flags because project guidelines prefer useSuspenseQuery for route-entry data; either replace useQuery with useSuspenseQuery and remove the enabled gating (wrapping this page in a Suspense boundary and handling loading via Suspense/Fallback), or keep useQuery but add a brief comment by the useQuery line referencing why schedules ?? [] fallback and the enabled flag are intentional; locate the call by the useQuery invocation and scheduleQueries.monthly({ year, month }) to apply the change.src/apis/club/queries.ts (1)
89-101:pageParam = 1기본값 중복Line 92의
pageParam = 1은 Line 93의initialPageParam: 1과 중복됩니다. 제거해도 동작에 영향 없습니다.♻️ 중복 기본값 제거
infiniteList: (params: ClubInfiniteListParams) => infiniteQueryOptions({ queryKey: clubQueryKeys.infinite.list(params), - queryFn: ({ pageParam = 1 }) => getClubs(buildClubListRequest(params, pageParam)), + queryFn: ({ pageParam }) => getClubs(buildClubListRequest(params, pageParam)), initialPageParam: 1,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/apis/club/queries.ts` around lines 89 - 101, The default pageParam in the infiniteList query is duplicated: remove the inline default in the queryFn parameter and rely on initialPageParam instead; specifically, update the infiniteList definition so queryFn uses ({ pageParam }) => getClubs(buildClubListRequest(params, pageParam)) (keeping initialPageParam: 1 and getNextPageParam logic intact) to avoid redundant defaults while preserving behavior in infiniteQueryOptions and buildClubListRequest.src/apis/chat/queries.ts (1)
18-31:chatRoomId!비널 단언 사용 확인
enabled: Boolean(chatRoomId)로 인해queryFn은chatRoomId가 있을 때만 실행되므로 안전합니다. 다만skipToken패턴을 고려해볼 수 있습니다.♻️ skipToken 사용 대안 (optional)
+import { infiniteQueryOptions, queryOptions, skipToken } from '@tanstack/react-query'; ... messages: (chatRoomId?: number, limit = 20) => infiniteQueryOptions({ queryKey: chatRoomId ? chatQueryKeys.messages(chatRoomId) : chatQueryKeys.disabledMessages(), - queryFn: ({ pageParam }) => - getChatMessages({ - chatRoomId: chatRoomId!, - page: pageParam, - limit, - }), + queryFn: chatRoomId + ? ({ pageParam }) => getChatMessages({ chatRoomId, page: pageParam, limit }) + : skipToken, initialPageParam: 1, getNextPageParam: (lastPage: ChatMessagesResponse) => lastPage.currentPage < lastPage.totalPage ? lastPage.currentPage + 1 : undefined, - enabled: Boolean(chatRoomId), }),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/apis/chat/queries.ts` around lines 18 - 31, The current messages factory uses a non-null assertion chatRoomId! inside queryFn even though enabled: Boolean(chatRoomId) prevents execution; replace the assertion with a safe guard or React Query's skipToken pattern so the queryFn never receives undefined—specifically update messages (the infiniteQueryOptions call) to only call getChatMessages when chatRoomId is defined (or use a skipToken-style queryKey and early-return/no-op queryFn), and keep the queryKey logic using chatQueryKeys.messages(chatRoomId) / chatQueryKeys.disabledMessages() and the enabled flag consistent to avoid the chatRoomId! assertion.src/apis/notification/index.ts (1)
2-2: 상대 경로 대신 path alias 사용 권장코딩 가이드라인에 따라
@/*path alias를 사용해주세요.♻️ 수정 제안
-import { apiClient } from '../client'; +import { apiClient } from '@/apis/client';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/apis/notification/index.ts` at line 2, Replace the relative import in src/apis/notification/index.ts with the project path alias: change the import of apiClient from "../client" to use the "@/apis/client" alias (i.e. import { apiClient } from '@/apis/client'), ensuring the apiClient symbol remains referenced unchanged.src/pages/Manager/ManagedMemberApplicationDetail/index.tsx (1)
7-11: params 변환 시 NaN 가능성
Number(params.clubId)와Number(params.userId)가undefined일 경우NaN이 됩니다. 라우트 설정상 항상 존재한다면 괜찮지만, 방어적 처리를 고려해볼 수 있습니다.🔧 방어적 처리 예시
const clubId = Number(params.clubId); const userId = Number(params.userId); + +if (Number.isNaN(clubId) || Number.isNaN(userId)) { + throw new Error('Invalid route parameters'); +}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Manager/ManagedMemberApplicationDetail/index.tsx` around lines 7 - 11, The code converts route params directly with Number(params.clubId) and Number(params.userId) which yields NaN if params are undefined; update the component to defensively parse and validate params from useParams() (e.g., use parseInt with radix or Number.isInteger checks) before calling managedClubQueries.memberApplicationDetail; if parsing fails, handle it early (return a fallback UI, throw a controlled error, or navigate away) so useSuspenseQuery isn't invoked with NaN values and functions like clubId, userId, useSuspenseQuery, and managedClubQueries.memberApplicationDetail only receive validated numeric IDs.src/apis/notification/mutations.ts (1)
2-2: 상대 경로 대신 경로 별칭 사용 필요코딩 가이드라인에 따라
@/*경로 별칭을 사용해주세요.🔧 수정 제안
-import { markInboxNotificationAsRead } from '.'; +import { markInboxNotificationAsRead } from '@/apis/notification';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/apis/notification/mutations.ts` at line 2, Replace the relative import for markInboxNotificationAsRead with the project path-alias form using `@/`*; locate the import statement that currently reads import { markInboxNotificationAsRead } from '.' and change it to use the alias (e.g., import { markInboxNotificationAsRead } from '@/...') so it complies with the codebase guideline for path aliases and resolves from the notification module symbol.src/apis/club/managedQueries.ts (2)
17-17: 상대 경로 대신 경로 별칭 사용 필요🔧 수정 제안
-} from '.'; +} from '@/apis/club';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/apis/club/managedQueries.ts` at line 17, Replace the relative import "from '.'" in managedQueries.ts with the project's configured path alias to avoid fragile relative paths; locate the import statement that ends with " } from '.';" and update it to use the TypeScript/webpack alias (e.g., "@/apis/club" or the project's specified alias) so the module resolution uses the alias configured in tsconfig/paths, then run a build/type-check to ensure the new alias resolves correctly.
63-90:queryFn에서null반환 시 타입 주의
infiniteQueryOptions에서queryFn이null을 반환할 수 있어getNextPageParam에서lastPage가null일 수 있습니다. 현재!lastPage체크로 잘 처리되어 있지만, 반환 타입이 명시적이지 않습니다.타입 안전성을 위해 반환 타입을 명시하거나, 소비하는 쪽에서 null 케이스를 인지하도록 문서화하는 것을 권장합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/apis/club/managedQueries.ts` around lines 63 - 90, The queryFn used in applications (inside infiniteQueryOptions with key managedClubQueryKeys.applicationsInfinite) can return null on NOT_FOUND_CLUB_RECRUITMENT, so explicitly type the queryFn/infiniteQueryOptions return type to Promise<YourResponseType | null> (the type returned by getManagedClubApplications) so callers know null is possible; update the generic/type annotation around queryFn or infiniteQueryOptions to include | null and keep getNextPageParam (referencing getNextPageParam and lastPage) handling the null case as-is.src/utils/ts/notification.ts (1)
52-54: 타입 단언 대신 타입 가드 고려
notification.type as NotificationInboxType타입 단언이 사용되었습니다.notification.type이 이미NotificationInboxType이라면 단언이 불필요하고, 아니라면 런타임에 예상치 못한 동작이 발생할 수 있습니다.현재
?? DEFAULT_NOTIFICATION_PRESENTATIONfallback이 있어 안전하지만, 타입 정의를 확인해보시기 바랍니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/utils/ts/notification.ts` around lines 52 - 54, The use of a type assertion in getInboxNotificationPresentation hides whether notification.type is actually a valid NotificationInboxType; replace the assertion with a runtime type guard (e.g., implement isNotificationInboxType(value): value is NotificationInboxType) and use it to look up NOTIFICATION_PRESENTATIONS, falling back to DEFAULT_NOTIFICATION_PRESENTATION when the guard returns false; ensure types InboxNotification, NotificationInboxType, NOTIFICATION_PRESENTATIONS, DEFAULT_NOTIFICATION_PRESENTATION and the new isNotificationInboxType are referenced so the change is localized to getInboxNotificationPresentation.src/pages/Manager/ManagedMemberList/index.tsx (1)
74-77: 하드코딩된 색상 값을 테마 토큰으로 대체 고려
#A5B3C1,#69BFDF,#C6CFD8등 여러 하드코딩된 색상이 사용되고 있습니다.src/styles/theme.css의 색상 토큰 사용을 권장합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Manager/ManagedMemberList/index.tsx` around lines 74 - 77, Replace hardcoded hex color literals (e.g., border-[`#A5B3C1`], and other occurrences like `#69BFDF`, `#C6CFD8`) with the corresponding theme tokens from src/styles/theme.css; update the className usages in this file (notably the container element with className including border-[`#A5B3C1`] and any nearby elements/icons such as RoleSelectorArrowDownIcon) to use the theme token names (e.g., border-[var(--token-name)] or the utility classes that map to those tokens) so colors derive from the centralized theme.src/components/layout/Header/headerConfig.ts (1)
46-47: 비교 연산자 일관성 확인 필요Line 46에서
==대신===를 사용하면 다른 config 항목들과 일관성이 유지됩니다.제안
- match: (pathname) => pathname == '/schedule', + match: (pathname) => pathname === '/schedule',🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/layout/Header/headerConfig.ts` around lines 46 - 47, The match function uses a loose equality operator; update the arrow function match to use strict equality (===) when comparing pathname to '/schedule' to maintain consistency with other config entries (look for the match: (pathname) => pathname == '/schedule' definition in headerConfig and change to ===).src/pages/Club/ClubDetail/components/ClubRecruitment.tsx (1)
91-93: 이미지 key로 index 사용이미지 목록이 정적이라면 괜찮지만, 이미지에 고유 ID(
image.id또는image.url)가 있다면 그것을 key로 사용하는 것이 더 안전합니다.제안
- {clubRecruitment.images.map((image, index) => ( - <img key={index} src={image.url} alt={`모집 공고 이미지 ${index + 1}`} className="w-full rounded-sm" /> + {clubRecruitment.images.map((image) => ( + <img key={image.url} src={image.url} alt="모집 공고 이미지" className="w-full rounded-sm" />🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Club/ClubDetail/components/ClubRecruitment.tsx` around lines 91 - 93, The map over clubRecruitment.images uses the array index as the React key which can cause rendering issues; change the key to a stable unique identifier such as image.id or image.url in the map callback (the mapping that renders <img> inside the clubRecruitment.images.map in ClubRecruitment.tsx) so each <img> uses a consistent unique key instead of index.src/pages/Club/ClubDetail/index.tsx (1)
3-3:cn()유틸리티 사용 권장코딩 가이드라인에 따르면 Tailwind 클래스 병합 시
cn()유틸리티를 사용해야 합니다.clsx직접 사용 대신cn()으로 통일하면 좋겠습니다.제안
-import clsx from 'clsx'; +import { cn } from '@/utils/ts/cn';className={cn( - clsx( - 'text-sub2 h-[38px] border-b-[1.6px] px-3 transition-colors', - currentTab === tab.key ? 'border-[`#69BFDF`] text-[`#69BFDF`]' : 'border-transparent text-indigo-300' - ) + 'text-sub2 h-[38px] border-b-[1.6px] px-3 transition-colors', + currentTab === tab.key ? 'border-[`#69BFDF`] text-[`#69BFDF`]' : 'border-transparent text-indigo-300' )}As per coding guidelines: "Use
cn()utility fromsrc/utils/ts/cn.tsto merge Tailwind CSS classes"Also applies to: 74-77
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Club/ClubDetail/index.tsx` at line 3, Replace the direct use of the clsx utility with the project's cn() utility: change the import of clsx to import cn from the cn utility and update all usages like className={clsx(...)} to className={cn(...)} (including the occurrences referenced around lines 74-77); ensure the import references the cn utility (src/utils/ts/cn.ts) and preserve any conditional class logic when converting each clsx call to cn().src/pages/Auth/SignUp/FinishStep.tsx (1)
11-13: 불필요한 null 체크
useSuspenseQuery는 데이터가 준비될 때까지 Suspense로 렌더링을 지연시키므로, 컴포넌트가 렌더링될 때myInfo는 항상 존재합니다. 이 null 체크는 실행되지 않는 코드입니다.제안
function FinishStep() { const navigate = useNavigate(); const { data: myInfo } = useSuspenseQuery(authQueries.myInfo()); const setUser = useAuthStore((state) => state.setUser); - if (!myInfo) { - return null; - } - const handleStart = () => {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Auth/SignUp/FinishStep.tsx` around lines 11 - 13, Remove the redundant null check for myInfo in the FinishStep component: because myInfo is loaded via useSuspenseQuery (or a suspense-backed hook) it will always be present when the component renders, so delete the if (!myInfo) return null; block and any dead branches depending on it; update any imports/logic in FinishStep that only existed to guard that branch and ensure the component relies directly on myInfo returned by the suspense query.src/pages/Club/ClubDetail/components/ClubMember.tsx (3)
64-66:useQuery대신useSuspenseQuery사용 검토 필요다른 컴포넌트들(
ClubAccount,CouncilDetail)은useSuspenseQuery를 사용하고 있는데, 이 컴포넌트만useQuery를 사용하고 있습니다. 일관성을 위해useSuspenseQuery로 변경하는 것이 좋습니다.또한 Line 66에서
clubMembers?.clubMembers.length는clubMembers가undefined일 때 런타임 에러가 발생할 수 있습니다.♻️ 수정 제안
- const { data: clubMembers } = useQuery(clubQueries.members(Number(clubId))); + const { data: clubMembers } = useSuspenseQuery(clubQueries.members(Number(clubId))); const members = clubMembers?.clubMembers ?? []; - const totalMembers = clubMembers?.clubMembers.length ?? memberCount; + const totalMembers = clubMembers?.clubMembers?.length ?? memberCount;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Club/ClubDetail/components/ClubMember.tsx` around lines 64 - 66, Replace useQuery with useSuspenseQuery for consistency (call useSuspenseQuery(clubQueries.members(Number(clubId)))) and keep the same destructuring name clubMembers; compute members as clubMembers?.clubMembers ?? [] and compute totalMembers safely using optional chaining and a fallback like clubMembers?.clubMembers?.length ?? memberCount so accessing .length cannot throw; update any related imports from useQuery to useSuspenseQuery and adjust variable usage in ClubMember component accordingly.
14-18: 하드코딩된 색상값 → 테마 토큰 사용 검토
#69BFDF,#F6DE8C,#FFB8B8등 하드코딩된 색상값이 사용되고 있습니다. 가능하다면src/styles/theme.css의 색상 토큰을 사용하는 것이 좋습니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Club/ClubDetail/components/ClubMember.tsx` around lines 14 - 18, Replace the hardcoded hex values in POSITION_BADGE_STYLES with theme tokens (CSS variables or theme constants) from your theme (e.g., values defined in src/styles/theme.css) so the badge colors follow the app theme; update the map entries in POSITION_BADGE_STYLES (referencing POSITION_BADGE_STYLES and PositionType) to use the appropriate token strings (like "var(--token-name)" or imported theme constant) instead of '#69BFDF', '#F6DE8C', '#FFB8B8', and ensure the type remains Partial<Record<PositionType, string>> and any components that consume these values still receive the token string.
1-2:cn()유틸리티 사용 권장코딩 가이드라인에 따르면 Tailwind 클래스 병합 시
clsx대신@/utils/ts/cn.ts의cn()유틸리티를 사용해야 합니다.♻️ 수정 제안
-import { useQuery } from '@tanstack/react-query'; -import clsx from 'clsx'; +import { useSuspenseQuery } from '@tanstack/react-query'; import { useParams } from 'react-router-dom'; import type { ClubMember, PositionType } from '@/apis/club/entity'; import { clubQueries } from '@/apis/club/queries'; +import { cn } from '@/utils/ts/cn';As per coding guidelines: "Use
cn()utility fromsrc/utils/ts/cn.tsto merge Tailwind CSS classes"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Club/ClubDetail/components/ClubMember.tsx` around lines 1 - 2, The file imports and uses the third-party clsx utility; replace that with your project's cn() helper: remove the import of clsx and add an import for cn from the local utility (src/utils/ts/cn.ts or '@/utils/ts/cn'), then update all usages of clsx(...) in this module (e.g., within the ClubMember component and any className expressions) to call cn(...) instead so Tailwind classes are merged via the project's standard helper.src/pages/Council/CouncilDetail/index.tsx (2)
28-30:useSuspenseQuery사용 시 불필요한 null 체크
useSuspenseQuery는 데이터가 로드될 때까지 suspend하므로,councilInfo가null인 경우는 발생하지 않습니다. 이 guard는 dead code입니다.♻️ 수정 제안
const { data: councilInfo } = useSuspenseQuery(councilQueries.info()); - if (!councilInfo) { - return <div>잘못된 경로입니다</div>; - } - return (🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Council/CouncilDetail/index.tsx` around lines 28 - 30, Remove the dead null-guard around councilInfo that returns the Korean error div; because councilInfo is provided by useSuspenseQuery and the component suspends until data exists, delete the if (!councilInfo) { return <div>잘못된 경로입니다</div>; } block (in CouncilDetail/index.tsx) and adjust any local code that assumed a nullable councilInfo type (e.g., refine prop/type annotations or remove unnecessary non-null assertions) so the component uses councilInfo as a present value.
3-3:cn()유틸리티 사용 권장Line 50에서
clsx를 직접 사용하고 있습니다. 코딩 가이드라인에 따라cn()을 사용하세요.As per coding guidelines: "Use
cn()utility fromsrc/utils/ts/cn.tsto merge Tailwind CSS classes"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Council/CouncilDetail/index.tsx` at line 3, Replace direct usage of the clsx utility in the CouncilDetail component with the project's cn() utility: remove the existing import of clsx and import the cn function from the shared utility (cn from src/utils/ts/cn.ts), then change all calls to clsx(...) to cn(...). Target the import statement that currently brings in clsx and every occurrence of the clsx symbol in CouncilDetail/index.tsx so the component uses cn(...) consistently.src/pages/Club/ClubDetail/components/ClubAccount.tsx (1)
6-8:clubIdundefined 처리 검토
useParams()의clubId가undefined일 경우Number(undefined)는NaN을 반환합니다. 라우트 구조상 항상 존재한다면 괜찮지만, 방어적 코딩을 위해 검증을 추가하는 것도 고려해보세요.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Club/ClubDetail/components/ClubAccount.tsx` around lines 6 - 8, The ClubAccount component uses useParams() and passes Number(clubId) into useSuspenseQuery(clubQueries.fee(...)) which will become NaN if clubId is undefined; update ClubAccount to validate clubId (from useParams) before calling useSuspenseQuery — e.g., check that clubId is a defined string and that Number(clubId) is a valid number (or use parseInt and !isNaN), and handle the invalid case by returning a fallback UI, throwing a descriptive error, or redirecting, so that clubQueries.fee is only called with a valid numeric id.src/pages/Manager/ManagedRecruitment/index.tsx (1)
48-49: Typography 토큰 사용 권장
text-sm,text-xs대신text-sub3,text-cap1등 시맨틱 typography 유틸리티 사용을 고려해주세요.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Manager/ManagedRecruitment/index.tsx` around lines 48 - 49, Replace the raw utility font-size classes on the title and content nodes with the semantic typography tokens: change the div rendering {title} currently using "text-sm" to the semantic token (e.g., "text-sub3") and change the div rendering {content} currently using "text-xs" to the semantic token (e.g., "text-cap1"), keeping existing leading and color classes (leading-[1.6], text-indigo-700/300) intact; update the two divs that render title and content in the ManagedRecruitment component accordingly so they use the project's semantic typography utilities.src/pages/Manager/ManagedApplicationList/index.tsx (1)
15-15: Path alias 사용 권장코딩 가이드라인에 따라 상대 경로 대신
@/*경로 alias를 사용해주세요.♻️ 제안
-import { useGetManagedApplications } from '../hooks/useManagedApplications'; +import { useGetManagedApplications } from '@/pages/Manager/hooks/useManagedApplications';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Manager/ManagedApplicationList/index.tsx` at line 15, 현재 파일의 상대경로 import of useGetManagedApplications uses "../hooks/useManagedApplications"; replace that relative import with the project path alias form (use the "@/..." alias) so the module import targets the same hooks module (useManagedApplications) via the alias instead of a relative path; update the import statement that references useGetManagedApplications to use the "@/…/useManagedApplications" alias form accordingly.src/pages/Club/ClubList/components/ClubCard.tsx (1)
35-61: 하드코딩된 색상값 대신 theme.css 토큰 사용 권장
#FEF3C7,#B45309등 하드코딩된 색상값이 있습니다. 코딩 가이드라인에 따르면src/styles/theme.css의 색상 토큰을 우선 사용해야 합니다.해당 상태별 색상이 테마에 없다면 CSS 변수로 추가하는 것을 고려해주세요.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Club/ClubList/components/ClubCard.tsx` around lines 35 - 61, The clubTag construction in ClubCard (the self-invoking that sets clubTag: ClubTag | null) uses hardcoded hex colors (e.g., '#FEF3C7', '#B45309', '#ECFDF5', '#047857', '#EFF6FF', '#1D4ED8'); replace those literal values with the theme CSS tokens (CSS variables) defined in src/styles/theme.css (e.g., use "var(--<token-name>)") so bgColor and textColor return token references instead of hex values; if the exact token does not exist for a given state (isPendingApproval, isAlwaysRecruiting, status === 'ONGOING'), add appropriately named tokens to theme.css and then reference them from ClubCard (and ensure the ClubTag type accepts string CSS variables).src/pages/Manager/ManagedRecruitmentForm/index.tsx (1)
4-4:cn()유틸리티 사용 권장
twMerge를 직접 import하기보다 이미 import된cn()유틸리티를 일관되게 사용하는 것이 좋습니다.cn()은clsx와tailwind-merge를 함께 처리합니다.♻️ 제안된 수정
-import { twMerge } from 'tailwind-merge';Line 128, 137의
twMerge호출을cn()으로 대체:-<div className={twMerge(sectionCardStyle, 'items-center py-10 text-center')}> +<div className={cn(sectionCardStyle, 'items-center py-10 text-center')}>As per coding guidelines: "Use
cn()utility fromsrc/utils/ts/cn.tsto merge Tailwind CSS classes"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Manager/ManagedRecruitmentForm/index.tsx` at line 4, Replace direct usage of twMerge with the project's cn() utility: remove the twMerge import and import cn() instead, then swap the two twMerge(...) calls inside the ManagedRecruitmentForm component for cn(...) so clsx + tailwind-merge behavior is preserved (the twMerge usages at the two className merge sites in ManagedRecruitmentForm should be updated to call cn()).src/components/layout/Header/components/SubpageHeader.tsx (1)
30-35: 서브페이지 title은 heading으로 두는 편이 낫습니다.지금 구조에선 실제 페이지 제목이
span이라 스크린리더 heading 탐색에서 빠집니다. 태그만h1으로 바꿔도 접근성이 좋아집니다.As per coding guidelines "접근성(aria-*, role, 키보드 탐색)이 적절히 처리되는지".예시
- <span className="text-sub1 truncate text-indigo-700">{title}</span> + <h1 className="text-sub1 truncate text-indigo-700">{title}</h1>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/layout/Header/components/SubpageHeader.tsx` around lines 30 - 35, The page title is rendered as a span which prevents screen readers from discovering it as a heading; update the SubpageHeader so the element showing title (currently the span that uses title, className and truncate styles, adjacent to the back button and ChevronLeftIcon and inside the smartBack onClick) is a proper heading element (e.g., h1 or appropriate semantic level) while keeping the existing className, truncation styling and layout so screen readers can find the page heading.src/apis/advertisement/queries.ts (1)
1-2: 새 API query 모듈은 alias import로 통일해주세요.
from '.'는 이 레이어의 import 규칙에서 바로 벗어납니다.@/apis/advertisement/...형태로 맞춰두면 이동/분리 때 추적이 훨씬 쉽습니다.As per coding guidelines "Use path alias
@/*for imports instead of relative paths".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/apis/advertisement/queries.ts` around lines 1 - 2, The import of getAdvertisements uses a relative alias ("from '.'"); update it to use the project path alias instead (e.g. import { getAdvertisements } from '@/apis/advertisement') so it follows the "Use path alias `@/`*" rule; locate the import of getAdvertisements in the queries module and replace the source string with the alias, keeping the named import intact and ensuring the module resolution still builds.src/components/notification/InAppNotificationToast.tsx (1)
45-60: 새 토스트에 raw 스타일 값이 너무 많습니다.
bg-[rgba(...)],text-[16px],text-[13px],text-[#5A6B7F]처럼 직접 값을 넣으면 이번 컴포넌트만 테마 토큰 체계에서 빠집니다. 배경/텍스트/CTA typography는src/styles/theme.css의 semantic color와text-body*/text-cap*토큰으로 맞춰두는 편이 이후 수정이 훨씬 쉽습니다.As per coding guidelines "Prioritize color tokens from
src/styles/theme.css(indigo-, blue-, background, primary, etc.)" and "Use typography tokens (text-h1throughtext-h5,text-sub1throughtext-sub4,text-body1throughtext-body3,text-cap1throughtext-cap2) fromsrc/styles/theme.css".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/notification/InAppNotificationToast.tsx` around lines 45 - 60, The toast uses raw style values; update InAppNotificationToast to use theme tokens instead: replace bg-[rgba(...)] with the appropriate semantic background token (e.g., background or surface token), replace the message paragraph's text-[16px] font class with a typography token like text-body1 or text-body2 and remove raw leading/whitespace overrides, and replace the button's text-[13px] and text-[`#5A6B7F`] with a caption/cta typography token (e.g., text-cap1/text-cap2) and a semantic color token (e.g., primary or text-muted) while keeping existing classes like rounded and padding; adjust className on the root element, the <p> that renders {message}, and the action <button> (identify these by InAppNotificationToast, the message variable, and onAction) so the component conforms to src/styles/theme.css tokens.src/components/layout/Header/presentation.ts (1)
1-3: 상대 경로 대신@/*별칭으로 통일해주세요.새 파일인데도 내부 import가 상대 경로로 들어와 있습니다. 이 파일도
@/components/layout/Header/...형태로 맞춰두면 이후 이동이나 리팩터링 때 경로 수정 범위가 줄어듭니다.As per coding guidelines "Use path alias
@/*for imports instead of relative paths"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/layout/Header/presentation.ts` around lines 1 - 3, Imports in presentation.ts use relative paths; update them to the project path alias form (e.g. "@/components/layout/Header/...") so they match the repo convention. Replace the three imports (DEFAULT_HEADER_TYPE, HEADER_CONFIGS from './headerConfig'; ROUTE_TITLES from './routeTitles'; and HeaderType from './types') to use the `@/`* alias pointing to their respective files under components/layout/Header, keeping the same imported symbols and types.src/components/layout/BottomNav/index.tsx (1)
88-91: 하단바 border 색상은 토큰으로 맞춰주세요.새 레이아웃 코드에 hex 값이 직접 들어와 있습니다. 테마 일관성을 위해 여기서도
border-text-100같은 토큰을 쓰는 편이 안전합니다.As per coding guidelines "Prioritize color tokens from `src/styles/theme.css` (indigo-*, blue-*, background, primary, etc.)"수정 예시
<nav ref={navRef} - className="fixed right-0 bottom-0 left-0 z-20 rounded-t-[20px] border-t border-[`#e0e0e0`] bg-white" + className="fixed right-0 bottom-0 left-0 z-20 rounded-t-[20px] border-t border-text-100 bg-white" >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/layout/BottomNav/index.tsx` around lines 88 - 91, Replace the hard-coded hex border color in the BottomNav component's nav element (the JSX with ref={navRef} and className="... border-t border-[`#e0e0e0`] ...") with the theme token class (e.g., use border-text-100 or the appropriate token from src/styles/theme.css) so the className no longer contains border-[`#e0e0e0`] but the token class; update the className string in the nav JSX in src/components/layout/BottomNav/index.tsx accordingly.src/pages/Manager/ManagedRecruitmentWrite/index.tsx (2)
4-4:twMerge대신cn()유틸리티 사용 권장코딩 가이드라인에 따라
@/utils/ts/cn.ts의cn()유틸리티를 사용해주세요.cn()은clsx와tailwind-merge를 결합한 유틸리티입니다.-import { twMerge } from 'tailwind-merge'; +import { cn } from '@/utils/ts/cn';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Manager/ManagedRecruitmentWrite/index.tsx` at line 4, Replace the tailwind-merge import and usages with the project's combined utility: remove the import of twMerge and any calls to twMerge(...) in ManagedRecruitmentWrite and instead import cn from '@/utils/ts/cn' and call cn(...) where merging/classname composition is needed; ensure any existing clsx-like arguments passed to twMerge remain compatible with cn() and update all occurrences of the symbol twMerge to cn to keep behavior consistent.
49-305: 페이지 로직을hooks/디렉토리로 분리 고려코딩 가이드라인에 따르면 페이지 로직은 페이지 전용
hooks/서브디렉토리로 분리하는 것이 권장됩니다. 현재 컴포넌트의 복잡도가 높으므로, 이미지 처리 로직이나 폼 상태 관리 등을 커스텀 훅으로 분리하면 가독성이 향상될 것입니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Manager/ManagedRecruitmentWrite/index.tsx` around lines 49 - 305, Extract the page logic from the ManagedRecruitmentWrite component into reusable hooks under a hooks/ subdirectory: create at least one hook for image preparation/management (pull out handleImageSelect, handlePrevImage, handleNextImage, handleDeleteImage, handleImageClick, isPreparingImages, images, currentImageIndex, setImages, setCurrentImageIndex and related URL.revokeObjectURL handling) and another hook for form/recruitment state (pull out startDate/endDate/startTime/endTime/isAlwaysRecruiting/content/isRecruitmentEnabled, applyExistingRecruitment, handleReset, handleContentChange, validation helpers like getDateErrorMessage/hasDateError, and the submit flow in handleSubmit including uploadImage, saveRecruitment and patchSettings). Keep ManagedRecruitmentWrite as a thin component that uses useImageManagement() and useRecruitmentForm(clubIdNumber, existingRecruitment, clubSettings, location, navigate, showToast) hooks, moving side effects (useEffect blocks that initialize from existingRecruitment and clubSettings) into the appropriate hooks so the component only renders UI and delegates logic to these named hooks.src/apis/auth/queries.ts (1)
8-8:signupPrefill쿼리 키가 다른 키들과 일관성이 없습니다다른 쿼리 키들은
authQueryKeys.all을 spread하는데,signupPrefill만 독립적인 배열을 사용합니다. 이로 인해authQueryKeys.all기반 invalidation 시signupPrefill은 제외될 수 있습니다.의도된 설계라면 무시해도 됩니다.
- signupPrefill: () => ['signup', 'prefill'] as const, + signupPrefill: () => [...authQueryKeys.all, 'signupPrefill'] as const,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/apis/auth/queries.ts` at line 8, The signupPrefill query key is inconsistent with others and should include authQueryKeys.all so it participates in broad invalidation; update the signupPrefill factory (signupPrefill) to return an array that spreads authQueryKeys.all and then 'prefill' (e.g. [...authQueryKeys.all, 'prefill'] as const) so it matches the pattern used by the other keys.src/utils/ts/viewport.ts (1)
17-17: 매직 넘버 상수화 고려
80이 뷰포트 컨텍스트 변경 임계값으로 사용되는데,KEYBOARD_OPEN_THRESHOLD_PX처럼 명명된 상수로 추출하면 의도가 더 명확해집니다.+const VIEWPORT_CONTEXT_CHANGE_THRESHOLD_PX = 80; + // inside setViewportHeight: - const hasViewportContextChanged = Math.abs(restingViewportWidth - w) > 80; + const hasViewportContextChanged = Math.abs(restingViewportWidth - w) > VIEWPORT_CONTEXT_CHANGE_THRESHOLD_PX;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/utils/ts/viewport.ts` at line 17, Extract the magic number 80 into a named constant (e.g., KEYBOARD_OPEN_THRESHOLD_PX or VIEWPORT_CONTEXT_CHANGE_THRESHOLD_PX) and replace the literal in the hasViewportContextChanged calculation (Math.abs(restingViewportWidth - w) > 80) with that constant; declare the constant near related viewport utilities so intent is clear and update any nearby comments or tests that rely on this threshold.src/components/layout/hooks/useLayoutElements.ts (1)
2-2: 상대 경로 대신@/경로 alias 사용 권장코딩 가이드라인에 따라
@/*경로 alias를 사용해주세요.-import { useLayoutBottomOverlayInset } from './useLayoutBottomOverlayInset'; +import { useLayoutBottomOverlayInset } from '@/components/layout/hooks/useLayoutBottomOverlayInset';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/layout/hooks/useLayoutElements.ts` at line 2, 현재 파일에서 useLayoutBottomOverlayInset을 상대경로로 import하고 있으니 코딩 가이드라인에 맞춰 경로 alias로 변경하세요: 교체 대상은 import { useLayoutBottomOverlayInset } from './useLayoutBottomOverlayInset';이며 이를 프로젝트의 `@/`* alias 규칙에 맞는 경로(예: '@/components/layout/hooks/useLayoutBottomOverlayInset')로 바꿔 사용하도록 수정하세요.src/pages/Chat/hooks/useChat.ts (1)
4-8: import 경로에서.ts확장자를 제거하세요.TypeScript/ESM 컨벤션상 import 경로에 확장자를 명시하지 않습니다.
♻️ 수정 제안
import { useCreateChatRoomMutation, useSendChatMessageMutation, useToggleChatMuteMutation, -} from '@/pages/Chat/hooks/useChatMutations.ts'; +} from '@/pages/Chat/hooks/useChatMutations';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Chat/hooks/useChat.ts` around lines 4 - 8, The import statement in useChat.ts incorrectly includes the .ts extension; update the import to remove the extension so it reads from '@/pages/Chat/hooks/useChatMutations' (without '.ts'), ensuring the three identifiers useCreateChatRoomMutation, useSendChatMessageMutation, and useToggleChatMuteMutation are imported from that path.src/pages/Notifications/index.tsx (1)
71-74: 타이포그래피 토큰 사용을 검토하세요.
text-[16px],text-[14px]등 하드코딩된 폰트 크기 대신text-body1,text-body2등 시맨틱 타이포그래피 토큰을 우선 검토해 주세요.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Notifications/index.tsx` around lines 71 - 74, Replace the hardcoded font-size utility classes on the notification text elements with semantic typography tokens: change the p element rendering notification.title (currently using "text-[16px] ... font-semibold") to use the semantic token (e.g., "text-body1 font-semibold ...") and change the p that renders getInboxNotificationMessage(notification) (currently "text-[14px] ... font-medium") to use "text-body2 font-medium ..."; ensure any additional utility classes (truncate, leading, color) remain, and confirm the design system tokens text-body1/text-body2 exist or map to the intended sizes in the global CSS/ tailwind config.src/utils/hooks/useAdvertisementInterval.ts (2)
97-97: 의존성 배열에 RefObject 포함 여부 검토.
firstItemRef,secondItemRef,mainRef,bottomNavRef는 RefObject로 참조가 안정적입니다. React에서 ref 객체 자체는 변경되지 않으므로 의존성 배열에서 제외해도 됩니다. 단, ESLint exhaustive-deps 규칙 때문에 포함했다면 현재 구현도 괜찮습니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/utils/hooks/useAdvertisementInterval.ts` at line 97, 요약: useAdvertisementInterval 훅의 useEffect 의존성 배열에 불필요한 RefObject들(firstItemRef, secondItemRef, mainRef, bottomNavRef)이 포함되어 있습니다; 수정하려면 useAdvertisementInterval 내부의 해당 useEffect에서 ref 객체(self)들을 의존성 배열에서 제거하고 대신 실제 변화에 따라 반응해야 하는 값들(예: enabled, itemCount)만 남기세요; 만약 ESLint의 exhaustive-deps 경고가 발생하면 ref.current 같은 실제 참조 값을 명시하거나 규칙 비활성화 주석을 사용해(`// eslint-disable-next-line react-hooks/exhaustive-deps`) 처리하면 됩니다.
50-53: 계산식 가독성 개선을 검토하세요.
Math.max(1, Math.max(2, Math.floor(...)) - 1)에서 외부Math.max(1, ...)는 내부Math.max(2, ...) - 1이 최소 1을 보장하므로 중복입니다.♻️ 수정 제안
const nextItemsPerAdvertisement = availableHeight <= 0 || slotHeight <= 0 ? DEFAULT_ITEMS_PER_ADVERTISEMENT - : Math.max(1, Math.max(2, Math.floor((availableHeight + gap) / slotHeight)) - 1); + : Math.max(2, Math.floor((availableHeight + gap) / slotHeight)) - 1;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/utils/hooks/useAdvertisementInterval.ts` around lines 50 - 53, The computation for nextItemsPerAdvertisement is using a redundant outer Math.max; replace the ternary's right-hand expression with Math.max(2, Math.floor((availableHeight + gap) / slotHeight)) - 1 so the inner Math.max(2, ...) already guarantees the minimum before subtracting 1. Update the assignment of nextItemsPerAdvertisement in useAdvertisementInterval.ts to use that simplified expression and remove the outer Math.max(1, ...).src/pages/Club/ClubList/components/AdvertisementCard.tsx (1)
25-31: 하드코딩된 색상 값 대신 디자인 토큰 사용을 검토하세요.
#1E2C3F,#EAF6FB,#2F6E83등 하드코딩된 색상이 있습니다. 가능하다면src/styles/theme.css의 색상 토큰 사용을 권장합니다. 광고 카드가 의도적으로 다른 스타일이라면 현재 구현도 괜찮습니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Club/ClubList/components/AdvertisementCard.tsx` around lines 25 - 31, AdvertisementCard contains hardcoded hex colors for the title, badge background and icon (see the JSX that renders advertisement.title, advertisement.description and the badge with CircleWarningIcon); replace those literals with your design tokens from theme (use the CSS variables or token-based utility classes instead of text-[`#1E2C3F`], bg-[`#EAF6FB`], text-[`#2F6E83`] and the inline style={{ color: '#2F6E83' }} on CircleWarningIcon), by switching the className/value to the corresponding token names (or var(--token-name)) and ensuring the tokens are available where the component is used so the visual remains identical but driven by theme variables.src/utils/ts/imagePreprocessor.ts (1)
137-146: Worker 응답 timeout 부재로 Promise가 무한 대기할 수 있습니다.Worker가 응답하지 않으면 Promise가 영원히 resolve/reject되지 않아 UI가 멈출 수 있습니다. timeout 추가를 권장합니다.
♻️ timeout 추가 제안
+const WORKER_TIMEOUT_MS = 30000; + async function runWorkerPreprocess(file: File, options: PrepareImageFileOptions) { // ... existing code ... return new Promise<ImagePreprocessResponse | null>((resolve, reject) => { + const timeoutId = setTimeout(() => { + pendingImagePreprocessRequests.delete(id); + resolve(null); // fallback to main thread + }, WORKER_TIMEOUT_MS); + try { - pendingImagePreprocessRequests.set(id, { reject, resolve }); + pendingImagePreprocessRequests.set(id, { + reject: (error) => { + clearTimeout(timeoutId); + reject(error); + }, + resolve: (value) => { + clearTimeout(timeoutId); + resolve(value); + }, + }); worker.postMessage(requestPayload); } catch { + clearTimeout(timeoutId); pendingImagePreprocessRequests.delete(id); resetImagePreprocessWorker(); resolve(null); } }); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/utils/ts/imagePreprocessor.ts` around lines 137 - 146, The Promise created for image preprocessing can hang indefinitely if the worker never responds; add a timeout (e.g., setTimeout) when creating the Promise in this block so that after a configurable delay you clean up pendingImagePreprocessRequests for the given id, call resetImagePreprocessWorker(), and resolve(null) to avoid UI hang. Store the timeout handle alongside the { resolve, reject } entry (pendingImagePreprocessRequests.set(id, { resolve, reject, timeoutHandle })) and ensure any existing resolution/rejection path that handles worker responses clears that timeout so you don't leak timers; reference pendingImagePreprocessRequests, resetImagePreprocessWorker, worker.postMessage, id, requestPayload, and ImagePreprocessResponse when implementing this change.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 288f3f65-652e-4f26-a4a7-069b87b1e4b2
⛔ Files ignored due to path filters (17)
public/apple-touch-icon-180x180.pngis excluded by!**/*.png,!public/**and included by**public/maskable-icon-512x512.pngis excluded by!**/*.png,!public/**and included by**public/pwa-192x192.pngis excluded by!**/*.png,!public/**and included by**public/pwa-512x512.pngis excluded by!**/*.png,!public/**and included by**public/pwa-64x64.pngis excluded by!**/*.png,!public/**and included by**src/assets/image/bottom-nav-home.pngis excluded by!**/*.png,!src/assets/**and included by**src/assets/image/chat-cat-header.pngis excluded by!**/*.png,!src/assets/**and included by**src/assets/image/notification-toast-approved.pngis excluded by!**/*.png,!src/assets/**and included by**src/assets/image/notification-toast-general.pngis excluded by!**/*.png,!src/assets/**and included by**src/assets/svg/bottom-nav-chat.svgis excluded by!**/*.svg,!src/assets/**and included by**src/assets/svg/bottom-nav-clubs.svgis excluded by!**/*.svg,!src/assets/**and included by**src/assets/svg/bottom-nav-mypage.svgis excluded by!**/*.svg,!src/assets/**and included by**src/assets/svg/bottom-nav-timer.svgis excluded by!**/*.svg,!src/assets/**and included by**src/assets/svg/chat-icon.svgis excluded by!**/*.svg,!src/assets/**and included by**src/assets/svg/notifications.svgis excluded by!**/*.svg,!src/assets/**and included by**src/assets/svg/person-icon.svgis excluded by!**/*.svg,!src/assets/**and included by**src/assets/svg/unread-notification.svgis excluded by!**/*.svg,!src/assets/**and included by**
📒 Files selected for processing (141)
src/App.tsxsrc/apis/advertisement/entity.tssrc/apis/advertisement/index.tssrc/apis/advertisement/mutations.tssrc/apis/advertisement/queries.tssrc/apis/auth/index.tssrc/apis/auth/mutations.tssrc/apis/auth/queries.tssrc/apis/chat/mutations.tssrc/apis/chat/queries.tssrc/apis/club/managedMutations.tssrc/apis/club/managedQueries.tssrc/apis/club/mutations.tssrc/apis/club/queries.tssrc/apis/council/queries.tssrc/apis/inquiry/mutations.tssrc/apis/notification/cache.tssrc/apis/notification/entity.tssrc/apis/notification/index.tssrc/apis/notification/mutations.tssrc/apis/notification/queries.tssrc/apis/schedule/queries.tssrc/apis/studyTime/hooks.tssrc/apis/studyTime/mutations.tssrc/apis/studyTime/queries.tssrc/apis/university/queries.tssrc/apis/upload/mutations.tssrc/components/common/Portal.tsxsrc/components/layout/BottomNav/index.tsxsrc/components/layout/Header/components/InfoHeader.tsxsrc/components/layout/Header/components/ManagerHeader.tsxsrc/components/layout/Header/components/NotificationBell.tsxsrc/components/layout/Header/components/SubpageHeader.tsxsrc/components/layout/Header/constants.tssrc/components/layout/Header/headerConfig.tssrc/components/layout/Header/index.tsxsrc/components/layout/Header/presentation.tssrc/components/layout/Header/routeTitles.tssrc/components/layout/Header/types.tssrc/components/layout/hooks/useLayoutBottomOverlayInset.tssrc/components/layout/hooks/useLayoutElements.tssrc/components/layout/index.tsxsrc/components/layout/layoutMetrics.tssrc/components/notification/InAppNotificationToast.tsxsrc/components/notification/InboxNotificationLayer.tsxsrc/components/notification/hooks/useInboxNotificationMutations.tssrc/components/notification/hooks/useInboxNotificationStream.tssrc/contexts/useLayoutElementsContext.tsxsrc/index.csssrc/pages/Auth/SignUp/ConfirmStep.tsxsrc/pages/Auth/SignUp/FinishStep.tsxsrc/pages/Auth/SignUp/StudentIdStep.tsxsrc/pages/Auth/SignUp/UniversityStep.tsxsrc/pages/Auth/SignUp/hooks/useInquiry.tssrc/pages/Auth/SignUp/hooks/useSignup.tssrc/pages/Auth/SignUp/hooks/useSignupPrefill.tssrc/pages/Auth/SignUp/hooks/useUniversity.tssrc/pages/Chat/ChatRoom.tsxsrc/pages/Chat/hooks/useChat.tssrc/pages/Chat/hooks/useChatMutations.tssrc/pages/Chat/hooks/useUnreadChatCount.tssrc/pages/Chat/index.tsxsrc/pages/Club/Application/applyCompletePage.tsxsrc/pages/Club/Application/clubFeePage.tsxsrc/pages/Club/Application/hooks/useApplyToClub.tssrc/pages/Club/Application/hooks/useClubApply.tssrc/pages/Club/Application/hooks/useGetClubFee.tssrc/pages/Club/ClubDetail/components/ClubAccount.tsxsrc/pages/Club/ClubDetail/components/ClubMember.tsxsrc/pages/Club/ClubDetail/components/ClubRecruitment.tsxsrc/pages/Club/ClubDetail/hooks/useCouncilNotices.tssrc/pages/Club/ClubDetail/hooks/useGetClubDetail.tssrc/pages/Club/ClubDetail/hooks/useGetClubMembers.tssrc/pages/Club/ClubDetail/hooks/useGetClubRecruitment.tssrc/pages/Club/ClubDetail/index.tsxsrc/pages/Club/ClubList/components/AdvertisementCard.tsxsrc/pages/Club/ClubList/components/ClubCard.tsxsrc/pages/Club/ClubList/hooks/useGetClubs.tssrc/pages/Club/ClubList/index.tsxsrc/pages/Club/ClubSearch/index.tsxsrc/pages/Council/CouncilDetail/components/CouncilNotice.tsxsrc/pages/Council/CouncilDetail/hooks/useGetCouncilInfo.tssrc/pages/Council/CouncilDetail/index.tsxsrc/pages/Council/CouncilNotice/hooks/useCouncilNoticeDetail.tssrc/pages/Home/components/CouncilNoticeSection.tsxsrc/pages/Home/components/HomeClubSection.tsxsrc/pages/Home/components/MiniSchedulePreview.tsxsrc/pages/Home/hooks/useGetHomeClubs.tssrc/pages/Home/hooks/useGetHomeCouncilNotices.tssrc/pages/Home/hooks/useGetNotificationToken.tssrc/pages/Home/hooks/useGetScheduleList.tssrc/pages/Home/hooks/useGetUpComingSchedule.tssrc/pages/Manager/ManagedAccount/index.tsxsrc/pages/Manager/ManagedApplicationDetail/index.tsxsrc/pages/Manager/ManagedApplicationList/index.tsxsrc/pages/Manager/ManagedClubList/index.tsxsrc/pages/Manager/ManagedClubProfile/index.tsxsrc/pages/Manager/ManagedMemberApplicationDetail/index.tsxsrc/pages/Manager/ManagedMemberList/index.tsxsrc/pages/Manager/ManagedRecruitment/index.tsxsrc/pages/Manager/ManagedRecruitmentForm/index.tsxsrc/pages/Manager/ManagedRecruitmentWrite/index.tsxsrc/pages/Manager/hooks/useManagedApplicationMutations.tssrc/pages/Manager/hooks/useManagedApplications.tssrc/pages/Manager/hooks/useManagedClubMutations.tssrc/pages/Manager/hooks/useManagedClubs.tssrc/pages/Manager/hooks/useManagedFee.tssrc/pages/Manager/hooks/useManagedMemberApplications.tssrc/pages/Manager/hooks/useManagedMemberMutations.tssrc/pages/Manager/hooks/useManagedMembers.tssrc/pages/Manager/hooks/useManagedRecruitment.tssrc/pages/Manager/hooks/useManagedSettings.tssrc/pages/Notifications/index.tsxsrc/pages/Schedule/components/ScheduleDetail.tsxsrc/pages/Schedule/hooks/useGetSchedules.tssrc/pages/Schedule/index.tsxsrc/pages/Timer/hooks/useStudyTime.tssrc/pages/Timer/hooks/useStudyTimeRanking.tssrc/pages/Timer/hooks/useStudyTimer.tssrc/pages/User/MyPage/components/UserInfoCard.tsxsrc/pages/User/MyPage/hooks/useLogout.tssrc/pages/User/MyPage/hooks/useWithdraw.tssrc/pages/User/MyPage/index.tsxsrc/pages/User/Profile/hooks/useMyInfo.tssrc/pages/User/Profile/hooks/useOAuthLinks.tssrc/pages/User/Profile/index.tsxsrc/pages/User/hooks/useAdminChatMutation.tssrc/stores/authStore.tssrc/utils/hooks/useAdvertisementInterval.tssrc/utils/hooks/useAdvertisements.tssrc/utils/hooks/useKeyboardHeight.tssrc/utils/hooks/useUploadImage.tssrc/utils/hooks/useViewportHeight.tssrc/utils/hooks/useViewportHeightLock.tssrc/utils/ts/accessToken.tssrc/utils/ts/dom.tssrc/utils/ts/imagePreprocessor.tssrc/utils/ts/imagePreprocessor.worker.tssrc/utils/ts/notification.tssrc/utils/ts/promise.tssrc/utils/ts/viewport.ts
💤 Files with no reviewable changes (25)
- src/pages/Auth/SignUp/hooks/useSignup.ts
- src/pages/Club/ClubDetail/hooks/useGetClubMembers.ts
- src/pages/Auth/SignUp/hooks/useInquiry.ts
- src/pages/Auth/SignUp/hooks/useSignupPrefill.ts
- src/pages/Auth/SignUp/hooks/useUniversity.ts
- src/pages/Club/Application/hooks/useGetClubFee.ts
- src/pages/Club/ClubDetail/hooks/useCouncilNotices.ts
- src/pages/Club/ClubDetail/hooks/useGetClubDetail.ts
- src/pages/Club/ClubList/hooks/useGetClubs.ts
- src/pages/Club/ClubDetail/hooks/useGetClubRecruitment.ts
- src/pages/Council/CouncilDetail/hooks/useGetCouncilInfo.ts
- src/pages/Home/hooks/useGetUpComingSchedule.ts
- src/pages/Home/hooks/useGetScheduleList.ts
- src/pages/Home/hooks/useGetHomeCouncilNotices.ts
- src/pages/Home/hooks/useGetNotificationToken.ts
- src/pages/Schedule/hooks/useGetSchedules.ts
- src/pages/Manager/hooks/useManagedSettings.ts
- src/pages/Manager/hooks/useManagedMemberApplications.ts
- src/pages/Manager/hooks/useManagedRecruitment.ts
- src/pages/Manager/hooks/useManagedClubs.ts
- src/utils/hooks/useKeyboardHeight.ts
- src/pages/User/Profile/hooks/useMyInfo.ts
- src/utils/hooks/useViewportHeight.ts
- src/pages/Manager/hooks/useManagedFee.ts
- src/pages/Manager/hooks/useManagedMembers.ts
| const { clubId } = useParams<{ clubId: string }>(); | ||
| const { data: settings } = useGetClubSettings(Number(clubId)); | ||
| const { data: settings } = useQuery(managedClubQueries.settings(Number(clubId))); |
There was a problem hiding this comment.
clubId가 undefined일 경우 NaN 전달 가능성
useParams에서 clubId가 undefined일 수 있습니다. Number(undefined)는 NaN을 반환하므로 쿼리에 잘못된 값이 전달될 수 있습니다.
🛡️ 제안
function ManagedRecruitment() {
const { clubId } = useParams<{ clubId: string }>();
+ if (!clubId) return null; // 또는 적절한 에러 처리
const { data: settings } = useQuery(managedClubQueries.settings(Number(clubId)));📝 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.
| const { clubId } = useParams<{ clubId: string }>(); | |
| const { data: settings } = useGetClubSettings(Number(clubId)); | |
| const { data: settings } = useQuery(managedClubQueries.settings(Number(clubId))); | |
| const { clubId } = useParams<{ clubId: string }>(); | |
| if (!clubId) return null; // 또는 적절한 에러 처리 | |
| const { data: settings } = useQuery(managedClubQueries.settings(Number(clubId))); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/Manager/ManagedRecruitment/index.tsx` around lines 12 - 13, The
code calls Number(clubId) directly which can pass NaN when clubId is undefined;
update to parse and guard before querying: read clubId from useParams, compute
const parsedClubId = clubId ? Number(clubId) : undefined (or use parseInt where
appropriate), then call useQuery(managedClubQueries.settings(parsedClubId as
number), { enabled: !!parsedClubId }) so managedClubQueries.settings and
useQuery are only executed when a valid numeric clubId exists; reference the
symbols useParams, clubId, Number(clubId), managedClubQueries.settings, and
useQuery when making this change.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/pages/Chat/hooks/useChatRoomScroll.ts (2)
117-130: ResizeObserver 조건부 생성 시 타입 명시 권장
resizeObserver변수의 타입이ResizeObserver | undefined로 추론되지만, 명시적 타입을 추가하면 가독성이 향상됩니다.♻️ 타입 명시 예시
- const resizeObserver = - typeof ResizeObserver !== 'undefined' - ? new ResizeObserver(() => { + const resizeObserver: ResizeObserver | undefined = + typeof ResizeObserver !== 'undefined' + ? new ResizeObserver(() => {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Chat/hooks/useChatRoomScroll.ts` around lines 117 - 130, The ResizeObserver created in useChatRoomScroll is currently relying on type inference; explicitly annotate the resizeObserver variable as ResizeObserver | undefined to improve readability and intent. Locate the declaration named resizeObserver in the useChatRoomScroll hook and change its declaration to include the explicit type annotation (ResizeObserver | undefined), keeping the conditional creation logic intact so the runtime behavior does not change.
111-137: ResizeObserver 구현 양호, 중복 scroll 호출 가능성 확인 필요SSR 환경 체크(
typeof ResizeObserver !== 'undefined)와requestAnimationFrame사용이 적절합니다.다만,
ChatRoom.tsx의handleSubmit에서 메시지 전송 후scrollToBottom()을 직접 호출하고, 동시에 textarea 축소로 인해 ResizeObserver가 트리거되어scrollToBottom()이 중복 호출될 수 있습니다. 기능상 idempotent하여 문제는 없지만, 불필요한 연산입니다.♻️ (선택적) 중복 호출 방지를 위한 debounce 또는 flag 패턴
간단한 방법으로
isScrollingRef를 추가하여 중복 호출을 방지할 수 있습니다:+ const isScrollingRef = useRef(false); const scrollToBottom = useCallback(() => { const container = scrollContainerRef.current; if (!container) return; + if (isScrollingRef.current) return; + isScrollingRef.current = true; container.scrollTop = container.scrollHeight; isNearBottomRef.current = true; + + requestAnimationFrame(() => { + isScrollingRef.current = false; + }); }, []);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Chat/hooks/useChatRoomScroll.ts` around lines 111 - 137, The ResizeObserver callback and ChatRoom.tsx's handleSubmit can both call scrollToBottom causing duplicate calls; add a guard (e.g., isScrollingRef) checked and set before invoking scrollToBottom so subsequent callers skip while a scroll is in progress, and clear the flag after the scroll completes (use requestAnimationFrame or a small timeout). Update the ResizeObserver logic in useLayoutEffect (where previousClientHeightRef, isNearBottomRef, chatMessagesLengthRef, scrollToBottom, chatRoomId are referenced) to check isScrollingRef before scheduling requestAnimationFrame(scrollToBottom), and update handleSubmit to set/check the same isScrollingRef around its direct call to scrollToBottom; alternatively implement a lightweight debounce around scrollToBottom if preferred.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/pages/Auth/SignUp/components/AgreementArrow.tsx`:
- Around line 33-35: The Link in AgreementArrow.tsx renders only a RightIcon so
screen readers lack context; update the <Link to={route} state={state}> (the
Link wrapping RightIcon/RightIcon component) to include an accessible name by
adding an appropriate aria-label (or aria-labelledby) that describes the link
target (e.g., "View agreement" or similar) and ensure the label is localized if
needed; keep the visual content as the icon and do not change styling or
navigation logic.
---
Nitpick comments:
In `@src/pages/Chat/hooks/useChatRoomScroll.ts`:
- Around line 117-130: The ResizeObserver created in useChatRoomScroll is
currently relying on type inference; explicitly annotate the resizeObserver
variable as ResizeObserver | undefined to improve readability and intent. Locate
the declaration named resizeObserver in the useChatRoomScroll hook and change
its declaration to include the explicit type annotation (ResizeObserver |
undefined), keeping the conditional creation logic intact so the runtime
behavior does not change.
- Around line 111-137: The ResizeObserver callback and ChatRoom.tsx's
handleSubmit can both call scrollToBottom causing duplicate calls; add a guard
(e.g., isScrollingRef) checked and set before invoking scrollToBottom so
subsequent callers skip while a scroll is in progress, and clear the flag after
the scroll completes (use requestAnimationFrame or a small timeout). Update the
ResizeObserver logic in useLayoutEffect (where previousClientHeightRef,
isNearBottomRef, chatMessagesLengthRef, scrollToBottom, chatRoomId are
referenced) to check isScrollingRef before scheduling
requestAnimationFrame(scrollToBottom), and update handleSubmit to set/check the
same isScrollingRef around its direct call to scrollToBottom; alternatively
implement a lightweight debounce around scrollToBottom if preferred.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 9ffda348-a183-4dd1-8040-22a401fcd4a7
📒 Files selected for processing (5)
src/pages/Auth/SignUp/TermStep.tsxsrc/pages/Auth/SignUp/components/AgreementArrow.tsxsrc/pages/Chat/hooks/useChatRoomScroll.tssrc/pages/User/MyPage/index.tsxsrc/utils/hooks/useSmartBack.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/pages/User/MyPage/index.tsx
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (4)
src/apis/notification/index.ts (1)
41-46: 반환 타입 명시 필요다른 함수들과 달리
markInboxNotificationAsRead는 반환 타입이 명시되어 있지 않아Promise<unknown>으로 추론됩니다. 코딩 가이드라인에 따라 응답 타입을 명시해주세요.♻️ 타입 명시 제안
-export const markInboxNotificationAsRead = async (notificationId: number) => { - const response = await apiClient.patch(`notifications/inbox/${notificationId}/read`, { +export const markInboxNotificationAsRead = async (notificationId: number): Promise<void> => { + await apiClient.patch<void>(`notifications/inbox/${notificationId}/read`, { requiresAuth: true, }); - return response; };또는 서버에서 응답 데이터가 있다면
entity.ts에 타입을 정의하고 사용해주세요.As per coding guidelines: "요청/응답 타입이 명시되어 있는지" 및 "Define API response types in the domain-specific
entity.tsfile"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/apis/notification/index.ts` around lines 41 - 46, markInboxNotificationAsRead currently lacks an explicit return type so TypeScript infers Promise<unknown>; update it to a concrete response type by defining/using a domain response in entity.ts (e.g., NotificationReadResponse or ApiResponse<void> if no payload) and annotate the function as returning Promise<ThatType>, then pass that type as the generic to apiClient.patch and import the type into src/apis/notification/index.ts so markInboxNotificationAsRead and its apiClient.patch call are strongly typed.src/utils/ts/imagePreprocessor.ts (1)
161-173: Timeout 핸들러에서 다른 pending request까지 null로 settle하는 로직 검토 필요한 요청이 timeout되면 worker를 종료하고 모든 다른 pending request도
null로 resolve합니다. 의도된 동작일 수 있으나, worker가 단순히 느린 경우(예: 대용량 이미지)에는 다른 요청들까지 불필요하게 실패할 수 있습니다.worker 종료 후 다른 요청들은 어차피 응답을 받지 못하므로 현재 로직이 맞지만, timeout된 요청만 실패시키고 worker를 유지하는 방식도 고려해볼 수 있습니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/utils/ts/imagePreprocessor.ts` around lines 161 - 173, The timeout handler currently takes the timed-out request via takePendingImagePreprocessRequest(id), calls resetImagePreprocessWorker(), resolves that request with null, and then calls settlePendingImagePreprocessRequests to resolve ALL other pending requests to null; change this so the handler only fails the timed-out request: remove or avoid calling settlePendingImagePreprocessRequests and do not tear down the worker unless you explicitly want to terminate it; keep only the logic that retrieves currentRequest, resolve it with null, and optionally decide separately whether to call resetImagePreprocessWorker() (if you choose to terminate the worker) so other pending requests remain pending and can still complete.src/utils/hooks/useAdvertisementInterval.ts (1)
60-97: cleanup 및 이벤트 리스너 관리 잘 되어있습니다.
cancelAnimationFrame(frameId)호출로 pending frame 정리ResizeObserver.disconnect()호출- 모든 event listener 제거
ResizeObserverfeature detection도 적절합니다.다만,
itemCount가 dependency array에 포함되어 있지만 effect 내부에서 직접 사용되지 않습니다. 의도적으로 아이템 수 변경 시 재측정을 트리거하는 것으로 보이는데, 간단한 주석이 있으면 의도가 명확해질 것 같습니다.💡 의도를 명확히 하는 주석 추가
- }, [bottomNavRef, enabled, firstItemRef, itemCount, mainRef, secondItemRef]); + // itemCount: 아이템 수 변경 시 재측정 트리거 목적으로 포함 + }, [bottomNavRef, enabled, firstItemRef, itemCount, mainRef, secondItemRef]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/utils/hooks/useAdvertisementInterval.ts` around lines 60 - 97, The useLayoutEffect in useAdvertisementInterval includes itemCount in its dependency array but does not reference it inside the effect; add a short explanatory comment next to the effect or near the dependency array (or inside the effect) stating that itemCount is included intentionally to force re-measurement when the number of items changes (so the measurement logic like measureAdvertisementInterval and observedElements updates run), referencing useAdvertisementInterval, measureAdvertisementInterval, and the itemCount dependency so future readers understand this is deliberate and not an accidental leftover.src/pages/Auth/SignUp/FinishStep.tsx (1)
8-14: Step 페이지 비즈니스 로직을 전용 훅으로 분리해 주세요.
useSuspenseQuery조회 +setUser+navigate가 페이지 컴포넌트에 직접 있어, Step 페이지 패턴 대비 응집도가 낮습니다.src/pages/Auth/SignUp/hooks/useFinishStep.ts로 분리하는 편이 좋습니다.♻️ 분리 예시
- const navigate = useNavigate(); - const { data: myInfo } = useSuspenseQuery(authQueries.myInfo()); - const setUser = useAuthStore((state) => state.setUser); - - const handleStart = () => { - setUser(myInfo); - navigate('/guide'); - }; + const { myInfo, handleStart } = useFinishStep();As per coding guidelines:
src/pages/**/*.{ts,tsx}: Separate page-specific logic into page-dedicatedhooks/directories.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Auth/SignUp/FinishStep.tsx` around lines 8 - 14, Extract the page logic into a new hook src/pages/Auth/SignUp/hooks/useFinishStep.ts: move the useSuspenseQuery(authQueries.myInfo()), the useAuthStore((s)=>s.setUser) call and useNavigate() into that hook, have it expose the fetched myInfo (or null) and a start/handleStart function that calls setUser(myInfo) and navigate('/guide'); then update FinishStep.tsx to import and use useFinishStep (replacing inline useSuspenseQuery, setUser, navigate and handleStart) so the page component only handles UI rendering.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/apis/studyTime/queries.ts`:
- Around line 21-22: The myRanking query key currently includes params.type
which causes refetches when the UI tab (type) changes even though queryFn only
uses params.sort; remove params.type from the key so it becomes
[...studyTimeQueryKeys.all, 'myRanking', params.sort] as const in the myRanking
function (keep MyStudyTimeRankingKeyParams unchanged), and ensure the query's
queryFn still reads params.sort and the select function continues to receive
params.type for client-side filtering (no change to select usage).
In `@src/pages/Manager/ManagedClubProfile/index.tsx`:
- Around line 120-125: The catch block currently wipes the selected image state;
instead, modify the catch handler in the image preprocessing flow so it only
resets the file input value and shows the toast, but preserves the current
preview/file state: remove or avoid calling
clearLocalPreviewUrl(localPreviewUrlRef), setImageFile(null) and
setImagePreview(''), keep e.target.value = '' and showToast('이미지 처리에 실패했습니다') so
the existing server image or previously chosen local preview remains visible.
In `@src/pages/Manager/ManagedMemberList/index.tsx`:
- Around line 232-236: The current useChangeManagedMemberPositionMutation call
(mutateAsync: changeMemberPosition with invalidateOnSuccess: false) disables
per-request invalidation and only relies on a single success-path invalidate,
which leaves the member list stale on partial failures; update the mutation
usage so that cache invalidation for the member list runs in the
onSettled/finally path (or call the single final invalidate unconditionally
after all position-change requests complete), ensuring changeMemberPosition
requests always trigger the final invalidate regardless of individual request
success or failure (apply the same fix to the other occurrence around lines
428-434).
In `@src/pages/Manager/ManagedRecruitmentForm/index.tsx`:
- Around line 37-38: The success UX for the "enableAfterSave" path is being
shown immediately after updateQuestions succeeds, which can mislead if
patchSettings (or the enableAfterSave update) fails; change the flow so that
after calling updateQuestions (useUpdateManagedClubQuestionsMutation ->
updateQuestions) you do NOT show the success toast or trigger navigation until
patchSettings (usePatchManagedClubSettingsMutation -> patchSettings) finishes
successfully; instead, chain the calls or await patchSettings inside the
updateQuestions onSuccess handler (or handle both promises) and only call the
toast/navigation when patchSettings resolves successfully, while handling and
reporting errors from patchSettings (including the enableAfterSave case)
appropriately.
In `@src/pages/Manager/ManagedRecruitmentWrite/index.tsx`:
- Around line 73-74: 현재 saveRecruitment 성공 직후 바로 성공 토스트 및 이동을 하고 있어 이후 호출되는
patchSettings 실패 시에도 사용자에게 성공으로 보입니다; 변경할 곳은 saveRecruitment와 patchSettings 호출
흐름(관련 훅: useUpsertManagedClubRecruitmentMutation,
usePatchManagedClubSettingsMutation, 함수/변수: saveRecruitment, patchSettings,
isPending, isSettingsPending). 수정 방법: saveRecruitment가 성공하면 즉시 토스트/이동하지 말고
patchSettings를 호출하고 그 결과를 기다려서 둘 다 성공했을 때만 성공 토스트와 네비게이션을 실행하도록 체이닝하거나 Promise
기반으로 병합 처리하고, patchSettings 실패 시 해당 에러를 적절히 노출(토스트/폼 에러)하도록 처리하세요; 또한
isPending/isSettingsPending 상태를 이용해 로딩 UI를 적절히 유지하세요.
In `@src/utils/ts/notification.ts`:
- Around line 80-88: normalizeInboxNotificationPath currently allows
protocol-relative or external URLs like "//evil.com" or "http://..." which can
cause open-redirects; update the function to only accept internal paths by
rejecting any input that (a) is empty after trim, (b) starts with "//", (c)
contains "://" anywhere, or (d) matches a URI scheme pattern (e.g.,
/^[a-zA-Z][\w+.-]*:/). If any of those checks match, return null; otherwise
ensure the returned value starts with a single leading slash (prepend "/" if
missing). Reference the function normalizeInboxNotificationPath and apply these
validation checks before returning the normalized path.
- Around line 52-53: The type guard isNotificationInboxType currently uses the
`in` operator which checks the prototype chain and can return true for inherited
keys; change it to use Object.hasOwn(NOTIFICATION_PRESENTATIONS, value) so only
own properties are considered. Locate the isNotificationInboxType function and
replace the `value in NOTIFICATION_PRESENTATIONS` check with an Object.hasOwn
call referencing NOTIFICATION_PRESENTATIONS and value to make the guard
accurate.
In `@src/utils/ts/promise.ts`:
- Around line 28-35: After awaiting iteratee(items[currentIndex], currentIndex)
a worker may return after another worker has set isAborted=true; update the
worker code (the block around iteratee, results, onResolved) to check isAborted
immediately after the await and before assigning results[currentIndex] or
calling onResolved, and skip those steps if isAborted is true; keep the existing
catch that sets isAborted=true and rethrows so new failures abort others, but
prevent in-flight completions from mutating results or invoking onResolved
(symbols to update: iteratee, items, currentIndex, results, onResolved,
isAborted).
---
Nitpick comments:
In `@src/apis/notification/index.ts`:
- Around line 41-46: markInboxNotificationAsRead currently lacks an explicit
return type so TypeScript infers Promise<unknown>; update it to a concrete
response type by defining/using a domain response in entity.ts (e.g.,
NotificationReadResponse or ApiResponse<void> if no payload) and annotate the
function as returning Promise<ThatType>, then pass that type as the generic to
apiClient.patch and import the type into src/apis/notification/index.ts so
markInboxNotificationAsRead and its apiClient.patch call are strongly typed.
In `@src/pages/Auth/SignUp/FinishStep.tsx`:
- Around line 8-14: Extract the page logic into a new hook
src/pages/Auth/SignUp/hooks/useFinishStep.ts: move the
useSuspenseQuery(authQueries.myInfo()), the useAuthStore((s)=>s.setUser) call
and useNavigate() into that hook, have it expose the fetched myInfo (or null)
and a start/handleStart function that calls setUser(myInfo) and
navigate('/guide'); then update FinishStep.tsx to import and use useFinishStep
(replacing inline useSuspenseQuery, setUser, navigate and handleStart) so the
page component only handles UI rendering.
In `@src/utils/hooks/useAdvertisementInterval.ts`:
- Around line 60-97: The useLayoutEffect in useAdvertisementInterval includes
itemCount in its dependency array but does not reference it inside the effect;
add a short explanatory comment next to the effect or near the dependency array
(or inside the effect) stating that itemCount is included intentionally to force
re-measurement when the number of items changes (so the measurement logic like
measureAdvertisementInterval and observedElements updates run), referencing
useAdvertisementInterval, measureAdvertisementInterval, and the itemCount
dependency so future readers understand this is deliberate and not an accidental
leftover.
In `@src/utils/ts/imagePreprocessor.ts`:
- Around line 161-173: The timeout handler currently takes the timed-out request
via takePendingImagePreprocessRequest(id), calls resetImagePreprocessWorker(),
resolves that request with null, and then calls
settlePendingImagePreprocessRequests to resolve ALL other pending requests to
null; change this so the handler only fails the timed-out request: remove or
avoid calling settlePendingImagePreprocessRequests and do not tear down the
worker unless you explicitly want to terminate it; keep only the logic that
retrieves currentRequest, resolve it with null, and optionally decide separately
whether to call resetImagePreprocessWorker() (if you choose to terminate the
worker) so other pending requests remain pending and can still complete.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: c1238209-1205-4761-b421-53dfc896a2f9
📒 Files selected for processing (31)
src/apis/advertisement/queries.tssrc/apis/auth/queries.tssrc/apis/club/managedQueries.tssrc/apis/club/queries.tssrc/apis/inquiry/mutations.tssrc/apis/notification/index.tssrc/apis/notification/mutations.tssrc/apis/notification/queries.tssrc/apis/studyTime/hooks.tssrc/apis/studyTime/mutations.tssrc/apis/studyTime/queries.tssrc/components/layout/Header/components/SubpageHeader.tsxsrc/components/layout/Header/headerConfig.tssrc/pages/Auth/SignUp/FinishStep.tsxsrc/pages/Chat/hooks/useChat.tssrc/pages/Club/Application/clubFeePage.tsxsrc/pages/Club/ClubDetail/components/ClubMember.tsxsrc/pages/Club/ClubDetail/components/ClubRecruitment.tsxsrc/pages/Council/CouncilDetail/index.tsxsrc/pages/Manager/ManagedAccount/index.tsxsrc/pages/Manager/ManagedApplicationList/index.tsxsrc/pages/Manager/ManagedClubProfile/index.tsxsrc/pages/Manager/ManagedMemberList/index.tsxsrc/pages/Manager/ManagedRecruitmentForm/index.tsxsrc/pages/Manager/ManagedRecruitmentWrite/index.tsxsrc/pages/Timer/hooks/useStudyTimer.tssrc/utils/hooks/useAdvertisementInterval.tssrc/utils/ts/imagePreprocessor.tssrc/utils/ts/notification.tssrc/utils/ts/promise.tssrc/utils/ts/viewport.ts
✅ Files skipped from review due to trivial changes (3)
- src/apis/advertisement/queries.ts
- src/apis/auth/queries.ts
- src/apis/notification/mutations.ts
🚧 Files skipped from review as they are similar to previous changes (16)
- src/pages/Club/ClubDetail/components/ClubRecruitment.tsx
- src/pages/Club/ClubDetail/components/ClubMember.tsx
- src/components/layout/Header/headerConfig.ts
- src/apis/inquiry/mutations.ts
- src/pages/Timer/hooks/useStudyTimer.ts
- src/pages/Manager/ManagedApplicationList/index.tsx
- src/apis/studyTime/hooks.ts
- src/pages/Council/CouncilDetail/index.tsx
- src/components/layout/Header/components/SubpageHeader.tsx
- src/apis/studyTime/mutations.ts
- src/pages/Manager/ManagedAccount/index.tsx
- src/utils/ts/viewport.ts
- src/pages/Chat/hooks/useChat.ts
- src/apis/notification/queries.ts
- src/pages/Club/Application/clubFeePage.tsx
- src/apis/club/queries.ts
Summary by CodeRabbit
새 기능
개선 사항