Conversation
* chore: 코드래빗 설정 파일 추가 * chore: 불필요한 tools 설정 제거
* feat: preMember 목록 분리 및 삭제 기능 추가 * chore: Pascal case * chore: 변경된 변수명 반영 * feat: 학번 숫자 필터링 추가
* chore: svg 추가 * feat: 채팅목록 알림 끄기 설정시 UI 추가 * feat: 목차 열릴시 애니메이션 추가
* feat: 카테고리 추가 * chore: 불필요한 코드 삭제
* feat: 정보 카드에서 정보 페이지로 이동하도록 기능 추가 * feat: 토스트 전역상태 추가 * refactor: query 훅 분리 및 onSuccess 콜백 옵션 제거 * refactor: query 훅 사용처 수정 * refactor: 사용처 수정 2 * fix: 채팅창 스크롤 초기화 문제 수정 및 줄바꿈 기준 변경 * feat: 토글 스위치 구현 * refactor: 모집 공고 관련 목록 페이지 디자인 수정 * feat: 컴포넌트 구현 및 icon 추가 * refactor: z-index 값 수정 * refactor: API 필드 변경 사항 반영 * refactor: 모집 공고 페이지 디자인 수정 및 라우트 백 수정 * refactor: 학교 목록에 없을 시 문구 디자인 수정 * fix: lint error * fix: 타입 변경 * feat: 모집 관련 페이지 API 추가사항 반영 * refactor: 토스트 타이머 클린업 추라 * refactor: 전역토스트 사용 변경 * refactor: 관리자 클럽 조회 훅 호출 범위 줄이기 * feat: onError handler add * chore: add button type and remove fragment
* feat: API 추가 및 연결 * chore: placeholder 제거
* fix: 채팅 스크롤 미작동 수정 * fix: 한글 출력으로 수정
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughToast를 Context 기반으로 전환하고 App 트리에 ToastProvider를 추가했습니다. 클럽 관련 타입을 재정의(ClubFeeResponse 추가, ClubApplyResponse 필드 변경)하고 club API에 사전회원 및 설정 엔드포인트(getPreMembers, deletePreMember, getClubSettings, patchClubSettings)를 추가했습니다. 매니저 훅 모듈을 분리·재구성하여 useManagerQuery 파일을 제거하고 다수의 특화 훅을 도입했습니다. 채팅 Room 인터페이스에 Possibly related PRs
🚥 Pre-merge checks | ✅ 1 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
src/pages/Manager/ManagedRecruitmentForm/index.tsx (1)
13-15:⚠️ Potential issue | 🟡 Minor
clubId파라미터 검증 필요다른 Manager 페이지들과 동일하게
Number(clubId)가 유효한지 검증이 필요합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Manager/ManagedRecruitmentForm/index.tsx` around lines 13 - 15, The code uses Number(clubId) directly when calling useManagedClubQuestions and useManagedClubQuestionsMutation without validating the route param; update the component to parse and validate clubId from useParams (e.g. const raw = clubId; const id = Number(raw) or parseInt(raw, 10)), check isNaN(id) (or !Number.isInteger(id) / id <= 0 as appropriate), and early-handle invalid ids by returning an error UI or redirecting (so neither useManagedClubQuestions nor useManagedClubQuestionsMutation are called with an invalid id); ensure references to managedClubQuestions, updateQuestions, isPending, and error remain gated behind the validated id.src/pages/Auth/SignUp/UniversityStep.tsx (1)
45-58:⚠️ Potential issue | 🟡 Minor
submitInquiry에러 핸들링 누락
onSuccess는 있지만onError콜백이 없어 실패 시 사용자 피드백이 없습니다.🛡️ 제안된 수정
submitInquiry( { type: 'UNIVERSITY_NOT_FOUND', content: fullContent }, { onSuccess: () => { closeInquiryModal(); setInquiryContent(''); showToast('문의가 접수되었습니다', 'success'); }, + onError: () => { + showToast('문의 접수에 실패했습니다', 'error'); + }, } );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Auth/SignUp/UniversityStep.tsx` around lines 45 - 58, The submitInquiry call in handleInquirySubmit lacks error handling; add an onError callback to the submitInquiry invocation (the one inside handleInquirySubmit) that closes or keeps the modal as appropriate, resets or preserves inquiryContent, and shows a failure toast via showToast with a helpful message (e.g., '문의 접수에 실패했습니다') and optionally log the error; ensure the onError receives the error param and uses it for context when calling showToast.src/pages/User/Profile/index.tsx (1)
100-105:⚠️ Potential issue | 🟡 Minor
withdraw()호출 시 에러 핸들링 누락탈퇴 요청 실패 시 사용자에게 피드백이 없습니다.
🛡️ 제안된 수정
<button - onClick={() => withdraw()} + onClick={() => withdraw(undefined, { + onError: () => showToast('탈퇴 처리에 실패했습니다', 'error'), + })} className="bg-primary text-h3 w-full rounded-lg py-3.5 text-center text-white" > 탈퇴하기 </button>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/User/Profile/index.tsx` around lines 100 - 105, The onClick currently calls withdraw() without awaiting or handling errors; wrap the call to withdraw in an async handler and add try/catch around the await withdraw() call (e.g., change onClick to an async function that awaits withdraw()), and in the catch block show user feedback (set an error state, display a toast/snackbar, or render an inline error message) and optionally re-enable/stop any loading state; reference the withdraw function and the button's onClick handler so you modify the click handler logic to properly await and surface failures to the user.src/pages/Manager/ManagedApplictaionDetail/index.tsx (1)
1-1:⚠️ Potential issue | 🟡 Minor디렉토리명 오타 수정 필요:
ManagedApplictaionDetail→ManagedApplicationDetail폴더명의
Applictaion을Application으로 수정하고,src/App.tsx31번 줄의 import 경로도 함께 업데이트하세요.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Manager/ManagedApplictaionDetail/index.tsx` at line 1, Rename the directory and references from ManagedApplictaionDetail to ManagedApplicationDetail (fix the misspelled "Applictaion" → "Application"), update the component file name if needed (index.tsx stays but folder name changes), and update the import statement that currently references ManagedApplictaionDetail in App.tsx to import from ManagedApplicationDetail so all imports and exports match the corrected folder name.src/pages/Manager/ManagedRecruitmentWrite/index.tsx (1)
177-191:⚠️ Potential issue | 🟠 Major
isFeeRequired상태 관리 필요
isFeeRequired가 하드코딩되어 있고, 기존 모집 공고 수정 시 원본 값이 무시되어 항상false로 덮어씌워집니다. 다음과 같이 개선하세요:
- 상태로 관리:
useState로isFeeRequired추가- 기존 데이터 로드:
applyExistingRecruitment에서existingRecruitment.isFeeRequired처리- UI 제어: 관리자가 이 값을 설정할 수 있도록 토글 필드 추가 (또는 기존 값 유지)
🤖 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 177 - 191, The code always sets isFeeRequired: false when calling saveRecruitment, so the original fee flag is lost; add a useState hook (e.g., const [isFeeRequired, setIsFeeRequired] = useState<boolean>(false)) and wire that state into saveRecruitment instead of the hardcoded false. In applyExistingRecruitment, initialize setIsFeeRequired(existingRecruitment.isFeeRequired) when loading existingRecruitment so edits preserve the original value. Add a UI control (toggle/checkbox) bound to isFeeRequired to allow admins to change it, and ensure the saveRecruitment calls use the isFeeRequired state in both branches where saveRecruitment is invoked.
🧹 Nitpick comments (20)
src/pages/Club/ClubDetail/components/ClubIntro.tsx (2)
18-21: 비동기 API 호출에 에러 핸들링 권장
createChatRoom실패 시response가 undefined일 수 있어response.chatRoomId접근에서 TypeError가 발생할 수 있습니다.🛡️ try-catch 추가 제안
const handleInquireClick = async () => { - const response = await createChatRoom(clubDetail.presidentUserId); - navigate(`/chats/${response.chatRoomId}`); + try { + const response = await createChatRoom(clubDetail.presidentUserId); + navigate(`/chats/${response.chatRoomId}`); + } catch (error) { + // Toast 등으로 사용자에게 에러 알림 + console.error('채팅방 생성 실패:', error); + } };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Club/ClubDetail/components/ClubIntro.tsx` around lines 18 - 21, The click handler handleInquireClick calls the async API createChatRoom and directly accesses response.chatRoomId, which can throw if createChatRoom fails or returns undefined; wrap the call in a try-catch, validate that the returned response object and response.chatRoomId exist before calling navigate(`/chats/${...}`), and handle failures (e.g., log the error, show a user-facing message/toast) so navigation only occurs on success.
60-66: button에type="button"명시 권장명시적 type 지정은 React 컨벤션이며, 향후 form 내부로 이동 시 의도치 않은 submit을 방지합니다.
✨ 제안
<button + type="button" onClick={handleInquireClick} className="bg-primary text-body3 flex items-center justify-center gap-1 rounded-sm py-3 text-white" >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Club/ClubDetail/components/ClubIntro.tsx` around lines 60 - 66, The button in ClubIntro with onClick={handleInquireClick} (the one rendering PaperPlaneIcon and "문의하기") lacks an explicit type, which can cause accidental form submits; update that <button> element to include type="button" to prevent implicit submit behavior when this component is placed inside a form, leaving the onClick handler and styling unchanged.src/pages/Manager/hooks/useManagedClubs.ts (1)
41-44: 캐시 무효화 누락 가능성
clubQueryKeys.detail(clubId)만 무효화하고managerClubQueryKeys.managedClub(clubId)는 무효화하지 않습니다. 관리자 페이지에서 수정 후 돌아왔을 때 stale 데이터가 표시될 수 있습니다.♻️ 제안된 수정
onSuccess: () => { showToast('클럽 정보가 수정되었습니다'); queryClient.invalidateQueries({ queryKey: clubQueryKeys.detail(clubId) }); + queryClient.invalidateQueries({ queryKey: managerClubQueryKeys.managedClub(clubId) }); navigate(-1); },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Manager/hooks/useManagedClubs.ts` around lines 41 - 44, The onSuccess handler in useManagedClubs only invalidates clubQueryKeys.detail(clubId), risking stale data in the admin view; update the onSuccess to also invalidate the manager cache by calling queryClient.invalidateQueries with managerClubQueryKeys.managedClub(clubId) (in addition to the existing clubQueryKeys.detail invalidation) before calling navigate(-1), so both public detail and manager-specific cached entries are refreshed.src/pages/Manager/ManagedClubProfile/index.tsx (1)
70-91: 비동기 처리 불일치
updateClubInfo가await없이 호출되어finally블록이 mutation 완료 전에 실행됩니다.isPending으로 버튼 상태를 관리하고 있지만,isUploading상태와의 타이밍 불일치가 발생할 수 있습니다.♻️ 제안된 수정
const handleSubmit = async () => { closeSubmitModal(); setIsUploading(true); try { let finalImageUrl = imagePreview; if (imageFile) { const result = await uploadImage(imageFile); finalImageUrl = result.fileUrl; } - updateClubInfo({ + await updateClubInfo({ description, imageUrl: finalImageUrl, location, introduce, }); } finally { setIsUploading(false); } };또는
isUploading상태를 제거하고isPending만 사용하는 것도 고려해보세요.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Manager/ManagedClubProfile/index.tsx` around lines 70 - 91, The submit flow currently calls updateClubInfo without awaiting it so the finally block (setIsUploading(false)) can run before the mutation completes; modify handleSubmit to await the updateClubInfo(...) call (i.e., await updateClubInfo({...})) so the upload/mutation completes before clearing isUploading, and keep closeSubmitModal(), setIsUploading(true) and the image upload flow as-is; alternatively, remove the local isUploading state and rely solely on the mutation's isPending state for button disabling—adjust setIsUploading usage accordingly if you choose that route.src/pages/User/MyPage/components/UserInfoCard.tsx (2)
102-105: 주석 처리된 코드 제거 권장주석 처리된 이미지 코드는 제거하는 것이 좋습니다. 필요시 git history에서 복구 가능합니다.
♻️ 제안된 수정
- {/* <img className="h-12 w-12 rounded-full" src={myInfo.imageUrl} alt="Member Avatar" /> */} <div className="flex h-12 w-12 items-center justify-center rounded-full bg-indigo-100 text-indigo-400"> {myInfo.name.charAt(0)} </div>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/User/MyPage/components/UserInfoCard.tsx` around lines 102 - 105, Remove the commented-out image element in UserInfoCard: delete the commented line that contains <img className="h-12 w-12 rounded-full" src={myInfo.imageUrl} alt="Member Avatar" /> and keep the active avatar fallback div that uses myInfo.name.charAt(0); this cleans up dead code while preserving the existing avatar logic (refer to the myInfo variable and the surrounding div with className="flex h-12 w-12 ...").
44-68: 훅 호출 최적화 고려
ManagerStats에서useGetManagedClubs()와useMyInfo()를 호출하는데, 부모UserInfoCard에서도useMyInfo()를 호출합니다. TanStack Query 캐싱으로 중복 요청은 없지만, props로 전달하면 더 명확한 데이터 흐름이 됩니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/User/MyPage/components/UserInfoCard.tsx` around lines 44 - 68, ManagerStats currently calls useGetManagedClubs() and useMyInfo() inside the component; instead change ManagerStats to receive the needed data via props (e.g., add props myInfo: ReturnTypeOfUseMyInfo and managedClubList: ReturnTypeOfUseGetManagedClubs or simply joinedClubsLength:number) and remove the internal calls to useGetManagedClubs and useMyInfo; update the parent UserInfoCard to call useMyInfo() and useGetManagedClubs() once and pass myInfo and managedClubList (or the derived joinedClubs length) into ManagerStats along with the existing onButtonClick prop so data flow is explicit and hooks aren’t duplicated.src/pages/Manager/ManagedRecruitmentForm/index.tsx (1)
17-27: 기본 질문 텍스트 상수화 권장하드코딩된 기본 질문을 상수로 분리하면 유지보수가 용이합니다.
♻️ 상수 분리
+const DEFAULT_QUESTION = { + question: '지원자의 전화번호를 입력해주세요.', + isRequired: true, +} as const; + function ManagedRecruitmentForm() { // ... const [questions, setQuestions] = useState<QuestionItem[]>(() => { if (managedClubQuestions.questions.length === 0) { - return [{ tempId: crypto.randomUUID(), question: '지원자의 전화번호를 입력해주세요.', isRequired: true }]; + return [{ tempId: crypto.randomUUID(), ...DEFAULT_QUESTION }]; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Manager/ManagedRecruitmentForm/index.tsx` around lines 17 - 27, The hardcoded default question string inside the initial state for questions should be extracted to a named constant for maintainability: create a constant (e.g., DEFAULT_PHONE_QUESTION or DEFAULT_MANAGED_QUESTION) and replace the literal '지원자의 전화번호를 입력해주세요.' used when managedClubQuestions.questions.length === 0 with that constant; update any related types/usages (QuestionItem initialization in the useState initializer, references to tempId generation with crypto.randomUUID()) so the behavior is unchanged but the default text is centralized.src/pages/Manager/ManagedAccount/index.tsx (2)
24-29:isFormValid로직 개선 가능
Number(amount)가 문자열일 때NaN이 될 수 있습니다. 현재amount.trim() !== ''체크가 있지만, 숫자 검증이 더 명확하면 좋겠습니다.♻️ 명확한 숫자 검증
const isFormValid = amount.trim() !== '' && - Number(amount) > 0 && + /^\d+$/.test(amount) && + Number(amount) > 0 && selectedBankId !== null && accountHolder.trim() !== '' && accountNumber.trim() !== '';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Manager/ManagedAccount/index.tsx` around lines 24 - 29, The isFormValid check should explicitly validate amount as a numeric value instead of relying on Number(amount) which can yield NaN; replace the current logic around isFormValid to parse the trimmed amount (e.g., const parsed = parseFloat(amount.trim())), ensure parsed is finite and > 0, and keep the existing checks for selectedBankId, accountHolder, and accountNumber (reference the isFormValid variable and the amount, selectedBankId, accountHolder, accountNumber identifiers).
56-62: 숫자 입력 필드에 입력값 검증 추가 권장
amount필드가text타입이라 숫자 외의 문자도 입력됩니다.ClubFeeRequest.amount가string타입이므로 숫자만 허용하는 검증이 필요합니다.♻️ 숫자만 허용하도록 수정
<input type="text" value={amount} - onChange={(e) => setAmount(e.target.value)} + onChange={(e) => { + const value = e.target.value; + if (value === '' || /^\d+$/.test(value)) { + setAmount(value); + } + }} placeholder="가입비를 입력해주세요" className="bg-indigo-5 w-full rounded-lg p-2 text-[15px] leading-6 font-semibold" />🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Manager/ManagedAccount/index.tsx` around lines 56 - 62, The amount input currently accepts any text; restrict and validate it so only numeric values are stored in the amount state (which maps to ClubFeeRequest.amount as a string). In the ManagedAccount component change the input handling for value={amount} and onChange={(e) => setAmount(e.target.value)} to validate/normalize the value (for example enforce type="number" or strip non-digit characters, optionally allow decimal by permitting one dot) and only call setAmount with the validated string; also add client-side feedback (e.g., disable submit or show error state) when the value is empty or invalid so downstream code receiving ClubFeeRequest.amount always gets a numeric string. Ensure you update the input attributes and the onChange handler referenced by amount and setAmount accordingly.src/pages/Manager/ManagedApplicationList/index.tsx (1)
56-71: 버튼 접근성 개선 권장
O,X텍스트만으로는 스크린 리더 사용자가 기능을 이해하기 어렵습니다.♻️ aria-label 추가
<button type="button" onClick={(e) => handleApprove(e, application.id)} disabled={isPending} + aria-label="승인" className="flex h-8 w-8 items-center justify-center rounded-full text-green-600 hover:bg-green-200 disabled:cursor-not-allowed disabled:opacity-50" > O </button> <button type="button" onClick={(e) => handleReject(e, application.id)} disabled={isPending} + aria-label="거절" className="flex h-8 w-8 items-center justify-center rounded-full text-red-600 hover:bg-red-200 disabled:cursor-not-allowed disabled:opacity-50" > X </button>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Manager/ManagedApplicationList/index.tsx` around lines 56 - 71, The approve/reject buttons use only "O" and "X" which is not screen-reader friendly; update the button elements rendered in ManagedApplicationList (the buttons that call handleApprove(e, application.id) and handleReject(e, application.id)) to include accessible labels—add aria-label attributes (e.g., aria-label={`Approve application ${application.id}`} and aria-label={`Reject application ${application.id}`}) and/or visually hidden text so screen readers convey the action while keeping the current visual "O"/"X" UI and preserve existing props like disabled and className.src/pages/Manager/hooks/useManagedApplications.ts (2)
60-74:useDeleteApplication이름이 실제 동작(reject)과 불일치이 함수는 실제로 지원을 거절(reject)합니다.
delete는 데이터 삭제를 암시하여 혼동될 수 있습니다.-export const useDeleteApplication = (clubId: number, options: ApplicationMutationOptions = {}) => { +export const useRejectApplication = (clubId: number, options: ApplicationMutationOptions = {}) => {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Manager/hooks/useManagedApplications.ts` around lines 60 - 74, The exported hook name useDeleteApplication is misleading because it performs a reject action; rename the hook to useRejectApplication (update the export and function identifier) and keep the existing behavior: mutationFn -> postClubApplicationReject(clubId, applicationId), onSuccess to showToast and invalidate applicationQueryKeys.managedClubApplications(clubId), and preserve the navigateBack option and navigate(-1) behavior; also update all imports/usages across the codebase to the new name (tests, components, and index exports) to avoid breaking references.
44-58: 함수명이 실제 동작과 불일치
useUpdateApplicationStatus는 실제로 승인(approve)만 수행합니다. 이름이 상태 업데이트를 암시하여 혼동될 수 있습니다.-export const useUpdateApplicationStatus = (clubId: number, options: ApplicationMutationOptions = {}) => { +export const useApproveApplication = (clubId: number, options: ApplicationMutationOptions = {}) => {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Manager/hooks/useManagedApplications.ts` around lines 44 - 58, The hook useUpdateApplicationStatus is misnamed because it only performs approval; rename it to useApproveApplication (and update its export/usages) or generalize it to accept a status parameter and call a more generic API (e.g., postClubApplicationUpdateStatus) instead; specifically, change the function name useUpdateApplicationStatus to useApproveApplication (or alter the signature to accept status and call a matching API instead of postClubApplicationApprove), update all call sites that import/use this hook, and keep the onSuccess behavior (showToast, queryClient.invalidateQueries using applicationQueryKeys.managedClubApplications(clubId), and optional navigate(-1)) consistent.src/utils/hooks/useSmartBack.ts (1)
44-62: 리팩토링 고려 사항 (선택)
/mypage/manager/경로 처리 로직이 길어지고 있습니다. 추후 route config 기반 매핑으로 분리하면 유지보수가 용이해집니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/utils/hooks/useSmartBack.ts` around lines 44 - 62, The /mypage/manager/ branch in useSmartBack.ts (the pathname.startsWith('/mypage/manager/') block) has lengthy conditional logic; refactor it to use a route-to-target mapping (route config) instead of many if/else checks: extract the pathname parsing (parts = pathname.split('/'), clubId = parts[3]) and replace the cascade that checks parts[4]/parts[5] with a lookup keyed by route patterns (e.g., 'info', 'recruitment', 'recruitment/:id', 'applications', 'applications/:id', 'members') that returns the corresponding targetPath (falling back to `/mypage/manager`), and update the code that sets targetPath in the function that contains this block so it reads from the mapping.src/pages/Manager/hooks/useManagedRecruitment.ts (2)
66-68:showToast타입 파라미터 누락Line 29, 43에서는
'success'를 전달하지만, Line 67에서는 타입이 없습니다. 일관성을 위해 추가를 권장합니다.♻️ 제안된 수정
onSuccess: () => { - showToast('질문이 수정되었습니다'); + showToast('질문이 수정되었습니다', 'success'); queryClient.invalidateQueries({ queryKey: recruitmentQueryKeys.managedClubQuestions(clubId) }); navigate(-1); },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Manager/hooks/useManagedRecruitment.ts` around lines 66 - 68, The onSuccess handler in useManagedRecruitment calls showToast without the type parameter while other calls pass 'success'; update the onSuccess callback to call showToast('질문이 수정되었습니다', 'success') so it matches the usage elsewhere (referencing showToast in the onSuccess block and the surrounding queryClient.invalidateQueries with recruitmentQueryKeys.managedClubQuestions(clubId)).
21-47:useCreateRecruitment와useUpdateRecruitment중복 로직두 훅이 동일한
mutationFn을 사용하고 toast 메시지만 다릅니다. 하나로 통합하거나 공통 로직을 추출하는 것을 고려해보세요.♻️ 통합 예시
export const useRecruitmentMutation = (clubId: number, mode: 'create' | 'update') => { const navigate = useNavigate(); const { showToast } = useToastContext(); const message = mode === 'create' ? '모집 공고가 생성되었습니다' : '모집 공고가 수정되었습니다'; return useMutation({ mutationKey: recruitmentQueryKeys.managedClubRecruitment(clubId), mutationFn: (data: ClubRecruitmentRequest) => putClubRecruitment(clubId, data), onSuccess: () => { showToast(message, 'success'); navigate(-1); }, }); };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Manager/hooks/useManagedRecruitment.ts` around lines 21 - 47, Both useCreateRecruitment and useUpdateRecruitment duplicate the same mutationFn and differ only in the success message; refactor by extracting shared logic into a single hook (e.g., useRecruitmentMutation) or a helper that encapsulates useMutation with mutationKey: recruitmentQueryKeys.managedClubRecruitment(clubId) and mutationFn: (data: ClubRecruitmentRequest) => putClubRecruitment(clubId, data), and accept a mode or message parameter to set the onSuccess behavior (showToast(...) and navigate(-1)); update call sites to use the new unified hook and remove the two duplicated hooks (keep references to useCreateRecruitment/useUpdateRecruitment only if you adapt them to wrap the new hook).src/pages/Manager/ManagedRecruitment/index.tsx (1)
43-62:patchSettings호출 시 에러 핸들링 부재토글 변경 시
patchSettings실패에 대한 사용자 피드백이 없습니다.onError콜백 추가를 권장합니다.♻️ 제안된 수정
<ToggleSwitch icon={MegaphoneSmIcon} label="모집공고" enabled={settings?.isRecruitmentEnabled ?? false} - onChange={(value) => patchSettings({ isRecruitmentEnabled: value })} + onChange={(value) => patchSettings( + { isRecruitmentEnabled: value }, + { onError: () => showToast('설정 변경에 실패했습니다', 'error') } + )} />🤖 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 43 - 62, The ToggleSwitch onChange handlers call patchSettings without any error handling; update each onChange for the three ToggleSwitches (labels "모집공고", "지원서", "회비") to call patchSettings and provide an onError callback (e.g., onError => showToast / setError state) that surfaces a user-facing error message when patchSettings fails; reference the ToggleSwitch components' onChange props and the patchSettings function so the failure path displays feedback and optionally reverts the toggle UI on error.src/pages/Manager/hooks/useManagedMembers.ts (1)
42-50: Mutation 훅들에onError핸들링 부재모든 mutation 훅에서
onError콜백이 없어 API 실패 시 사용자 피드백이 없습니다. 공통 에러 핸들링 추가를 권장합니다.♻️ 예시 (useTransferPresident)
return useMutation({ mutationFn: (data: TransferPresidentRequest) => postTransferPresident(clubId, data), onSuccess: () => { showToast('회장이 위임되었습니다'); queryClient.invalidateQueries({ queryKey: memberQueryKeys.managedMembers(clubId) }); queryClient.invalidateQueries({ queryKey: memberQueryKeys.managedClub(clubId) }); navigate(-1); }, + onError: () => { + showToast('회장 위임에 실패했습니다', 'error'); + }, });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Manager/hooks/useManagedMembers.ts` around lines 42 - 50, The mutation hook useManagedMembers lacks onError handling so API failures give no user feedback; add an onError callback to the useMutation returned by useManagedMembers that logs/handles the error and shows a user-facing message (e.g., via showToast) when postTransferPresident fails, and optionally perform any rollback or state cleanup similar to the onSuccess flow (ensure you still call queryClient.invalidateQueries for memberQueryKeys.managedMembers(clubId) and memberQueryKeys.managedClub(clubId) only on success). Reference the useManagedMembers mutationFn, postTransferPresident, showToast, queryClient.invalidateQueries and navigate to implement consistent error handling across mutation hooks.src/pages/Manager/ManagedApplictaionDetail/index.tsx (1)
36-46:showToast호출 시 타입 파라미터 누락에러 toast에
'error'타입을 명시하면 일관성이 향상됩니다. Profile 페이지에서는showToast('메시지', 'error')형태로 사용 중입니다.♻️ 제안된 수정
const handleApprove = () => { approve(application.applicationId, { onSuccess: closeApprove, - onError: () => showToast('요청 처리에 실패했습니다'), + onError: () => showToast('요청 처리에 실패했습니다', 'error'), }); }; const handleReject = () => { reject(application.applicationId, { onSuccess: closeReject, - onError: () => showToast('요청 처리에 실패했습니다'), + onError: () => showToast('요청 처리에 실패했습니다', 'error'), }); };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Manager/ManagedApplictaionDetail/index.tsx` around lines 36 - 46, The onError callbacks for approve and reject currently call showToast with only a message; update both to pass the error toast type as the second argument (i.e., showToast('요청 처리에 실패했습니다', 'error')). Locate the approve(...) and reject(...) calls in this file (the onError lambdas that reference showToast) and change both callbacks to include the 'error' type, keeping existing onSuccess handlers (closeApprove, closeReject) unchanged.src/pages/Manager/ManagedMemberList/index.tsx (1)
211-214: 주석 처리된 코드 제거 권장주석 처리된
<img>태그가 여러 곳(Lines 211, 306, 358)에 있습니다. 사용하지 않는 코드는 제거하는 것이 좋습니다.🤖 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 211 - 214, Remove the unused commented-out <img> tags in the ManagedMemberList component and replace them with the existing avatar fallback div; specifically delete the commented line that references member.imageUrl (the commented <img className="h-10 w-10 rounded-full object-cover" src={member.imageUrl} alt={member.name} />) and any other identical commented <img> occurrences in this file so only the live JSX (the div using member.name.charAt(0>) remains; this cleans up dead code without changing rendering behavior.src/pages/Manager/ManagedRecruitmentWrite/index.tsx (1)
34-37:parseDateDot유효성 검사 부재잘못된 형식의 날짜 문자열이 들어오면 Invalid Date가 생성됩니다.
🛡️ 제안된 수정
function parseDateDot(dateStr: string): Date { + if (!dateStr || !/^\d{4}\.\d{2}\.\d{2}$/.test(dateStr)) { + return new Date(); + } const [year, month, day] = dateStr.split('.').map(Number); return new Date(year, month - 1, day); }🤖 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 34 - 37, The parseDateDot function currently constructs a Date from any input and can yield Invalid Date; update parseDateDot to validate the input string and parsed numeric components before returning a Date: first assert the input matches /^\d{4}\.\d{1,2}\.\d{1,2}$/ or split and ensure you get exactly three numeric parts, then convert to numbers and check Number.isFinite for year/month/day, enforce month in 1..12 and day in 1..31, create the Date(year, month-1, day) and finally verify the produced date matches the original year/month/day (date.getFullYear() === year etc.); if validation fails, either throw a descriptive Error or return null/undefined consistently for the callers to handle.
🤖 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/chat/entity.ts`:
- Line 11: The type for the Chat entity's isMuted field is currently declared as
a required boolean but usage in UI is inconsistent; make the declaration
optional (change isMuted: boolean to isMuted?: boolean in the Chat entity) and
then update all callers (e.g., ChatHeader.tsx and Chat/index.tsx) to handle
undefined consistently (either keep the ?? false fallback everywhere or remove
fallbacks after making backend always return a value) so type and runtime usage
align; ensure references to isMuted in components use the agreed pattern and
update any lint/typescript errors accordingly.
In `@src/components/layout/Header/components/ChatHeader.tsx`:
- Around line 75-81: The participant avatar <img> in ChatHeader (inside the
clubMembers.map render) is missing an alt attribute; update the <img> element in
the ChatHeader component to include a descriptive alt (e.g., member.name or
`${member.name} avatar`) so screen readers get meaningful text, and ensure it
still uses member.imageUrl for src and preserves the existing key and className.
In `@src/pages/Auth/SignUp/ConfirmStep.tsx`:
- Around line 57-59: The onError callback in ConfirmStep.tsx calls showToast
without specifying a type so it defaults to 'success'; update the onError
handler to call showToast with the 'error' type (e.g., showToast('문의 전송에
실패했습니다', 'error')) so the toast renders as an error. Ensure you change the
showToast invocation inside the onError block in the ConfirmStep component to
include the explicit 'error' type.
In `@src/pages/Club/Application/components/AccountInfo.tsx`:
- Around line 33-40: The copy button currently attempts
navigator.clipboard.writeText(accountInfo.accountNumber ?? '') and will show a
success toast even when accountInfo.accountNumber is null/empty; update the
button logic so it first checks that accountInfo.accountNumber is non-empty (or
render the button disabled when missing) and only calls
navigator.clipboard.writeText and showToast('계좌번호가 복사되었습니다') when a valid
account number exists; otherwise show a failure toast or keep the button
disabled. Target the onClick handler for the button and the button's disabled
prop, and keep showToast usage and clipboard write inside the guarded branch.
In `@src/pages/Manager/hooks/useManagedMembers.ts`:
- Around line 93-107: In useAddPreMember, remove the navigate(-1) call from the
onSuccess handler so adding a pre-member only shows the toast and invalidates
queries (memberQueryKeys.managedMembers and preMembersList) without leaving the
current page; also remove the useNavigate() import/const (navigate) if it
becomes unused after this change.
In `@src/pages/Manager/hooks/useManagedSettings.ts`:
- Around line 10-25: The hook useGetClubSettings currently calls getClubSettings
with a possibly invalid clubId which can become NaN; add a query enabled guard
so the query only runs when clubId is a valid number (e.g. add enabled:
!Number.isNaN(Number(clubId))) to the useQuery options for useGetClubSettings
(reference settingsQueryKeys.clubSettings and getClubSettings); alternatively,
validate clubId before calling this hook in the parent component so
useGetClubSettings never receives an invalid id.
In `@src/pages/Manager/ManagedAccount/index.tsx`:
- Line 10: clubIdNumber is computed with Number(clubId) which yields NaN when
clubId is undefined; update the logic around the const clubIdNumber to guard
against missing route params by validating clubId first (e.g., check clubId !==
undefined && clubId !== ''), parse it safely (parseInt/clamp or Number(clubId)
only after the check), and handle the invalid case by returning early or using a
default/fallback (e.g., redirect, show an error state, or set clubIdNumber =
null and guard downstream). Ensure all references to clubIdNumber (the const)
are updated to handle the possible null/undefined/NaN case.
In `@src/pages/Manager/ManagedApplicationList/index.tsx`:
- Line 14: ManagedApplicationList uses const clubId = Number(params.clubId)
without NaN validation; mirror the ManagedAccount approach by validating
params.clubId (e.g., parse/Number then Number.isNaN check) and handle invalid
values the same way ManagedAccount does (return/redirect/error response). Update
the clubId parsing/validation in index.tsx (the clubId variable derived from
params.clubId) to perform the NaN check and the same fallback/error handling
logic as in ManagedAccount.
In `@src/pages/Manager/ManagedRecruitment/index.tsx`:
- Around line 14-16: Validate clubId from useParams before converting to Number
and calling the hooks: compute a parsedId (e.g. const parsedId = clubId ?
Number(clubId) : undefined), check Number.isNaN(parsedId) or parsedId ===
undefined and return/redirect/show an error early, and only call
useGetClubSettings(parsedId) and usePatchClubSettings(parsedId) (or call them
with parsedId and an enabled flag) when parsedId is a valid number so you never
pass NaN to those hooks.
---
Outside diff comments:
In `@src/pages/Auth/SignUp/UniversityStep.tsx`:
- Around line 45-58: The submitInquiry call in handleInquirySubmit lacks error
handling; add an onError callback to the submitInquiry invocation (the one
inside handleInquirySubmit) that closes or keeps the modal as appropriate,
resets or preserves inquiryContent, and shows a failure toast via showToast with
a helpful message (e.g., '문의 접수에 실패했습니다') and optionally log the error; ensure
the onError receives the error param and uses it for context when calling
showToast.
In `@src/pages/Manager/ManagedApplictaionDetail/index.tsx`:
- Line 1: Rename the directory and references from ManagedApplictaionDetail to
ManagedApplicationDetail (fix the misspelled "Applictaion" → "Application"),
update the component file name if needed (index.tsx stays but folder name
changes), and update the import statement that currently references
ManagedApplictaionDetail in App.tsx to import from ManagedApplicationDetail so
all imports and exports match the corrected folder name.
In `@src/pages/Manager/ManagedRecruitmentForm/index.tsx`:
- Around line 13-15: The code uses Number(clubId) directly when calling
useManagedClubQuestions and useManagedClubQuestionsMutation without validating
the route param; update the component to parse and validate clubId from
useParams (e.g. const raw = clubId; const id = Number(raw) or parseInt(raw,
10)), check isNaN(id) (or !Number.isInteger(id) / id <= 0 as appropriate), and
early-handle invalid ids by returning an error UI or redirecting (so neither
useManagedClubQuestions nor useManagedClubQuestionsMutation are called with an
invalid id); ensure references to managedClubQuestions, updateQuestions,
isPending, and error remain gated behind the validated id.
In `@src/pages/Manager/ManagedRecruitmentWrite/index.tsx`:
- Around line 177-191: The code always sets isFeeRequired: false when calling
saveRecruitment, so the original fee flag is lost; add a useState hook (e.g.,
const [isFeeRequired, setIsFeeRequired] = useState<boolean>(false)) and wire
that state into saveRecruitment instead of the hardcoded false. In
applyExistingRecruitment, initialize
setIsFeeRequired(existingRecruitment.isFeeRequired) when loading
existingRecruitment so edits preserve the original value. Add a UI control
(toggle/checkbox) bound to isFeeRequired to allow admins to change it, and
ensure the saveRecruitment calls use the isFeeRequired state in both branches
where saveRecruitment is invoked.
In `@src/pages/User/Profile/index.tsx`:
- Around line 100-105: The onClick currently calls withdraw() without awaiting
or handling errors; wrap the call to withdraw in an async handler and add
try/catch around the await withdraw() call (e.g., change onClick to an async
function that awaits withdraw()), and in the catch block show user feedback (set
an error state, display a toast/snackbar, or render an inline error message) and
optionally re-enable/stop any loading state; reference the withdraw function and
the button's onClick handler so you modify the click handler logic to properly
await and surface failures to the user.
---
Nitpick comments:
In `@src/pages/Club/ClubDetail/components/ClubIntro.tsx`:
- Around line 18-21: The click handler handleInquireClick calls the async API
createChatRoom and directly accesses response.chatRoomId, which can throw if
createChatRoom fails or returns undefined; wrap the call in a try-catch,
validate that the returned response object and response.chatRoomId exist before
calling navigate(`/chats/${...}`), and handle failures (e.g., log the error,
show a user-facing message/toast) so navigation only occurs on success.
- Around line 60-66: The button in ClubIntro with onClick={handleInquireClick}
(the one rendering PaperPlaneIcon and "문의하기") lacks an explicit type, which can
cause accidental form submits; update that <button> element to include
type="button" to prevent implicit submit behavior when this component is placed
inside a form, leaving the onClick handler and styling unchanged.
In `@src/pages/Manager/hooks/useManagedApplications.ts`:
- Around line 60-74: The exported hook name useDeleteApplication is misleading
because it performs a reject action; rename the hook to useRejectApplication
(update the export and function identifier) and keep the existing behavior:
mutationFn -> postClubApplicationReject(clubId, applicationId), onSuccess to
showToast and invalidate applicationQueryKeys.managedClubApplications(clubId),
and preserve the navigateBack option and navigate(-1) behavior; also update all
imports/usages across the codebase to the new name (tests, components, and index
exports) to avoid breaking references.
- Around line 44-58: The hook useUpdateApplicationStatus is misnamed because it
only performs approval; rename it to useApproveApplication (and update its
export/usages) or generalize it to accept a status parameter and call a more
generic API (e.g., postClubApplicationUpdateStatus) instead; specifically,
change the function name useUpdateApplicationStatus to useApproveApplication (or
alter the signature to accept status and call a matching API instead of
postClubApplicationApprove), update all call sites that import/use this hook,
and keep the onSuccess behavior (showToast, queryClient.invalidateQueries using
applicationQueryKeys.managedClubApplications(clubId), and optional navigate(-1))
consistent.
In `@src/pages/Manager/hooks/useManagedClubs.ts`:
- Around line 41-44: The onSuccess handler in useManagedClubs only invalidates
clubQueryKeys.detail(clubId), risking stale data in the admin view; update the
onSuccess to also invalidate the manager cache by calling
queryClient.invalidateQueries with managerClubQueryKeys.managedClub(clubId) (in
addition to the existing clubQueryKeys.detail invalidation) before calling
navigate(-1), so both public detail and manager-specific cached entries are
refreshed.
In `@src/pages/Manager/hooks/useManagedMembers.ts`:
- Around line 42-50: The mutation hook useManagedMembers lacks onError handling
so API failures give no user feedback; add an onError callback to the
useMutation returned by useManagedMembers that logs/handles the error and shows
a user-facing message (e.g., via showToast) when postTransferPresident fails,
and optionally perform any rollback or state cleanup similar to the onSuccess
flow (ensure you still call queryClient.invalidateQueries for
memberQueryKeys.managedMembers(clubId) and memberQueryKeys.managedClub(clubId)
only on success). Reference the useManagedMembers mutationFn,
postTransferPresident, showToast, queryClient.invalidateQueries and navigate to
implement consistent error handling across mutation hooks.
In `@src/pages/Manager/hooks/useManagedRecruitment.ts`:
- Around line 66-68: The onSuccess handler in useManagedRecruitment calls
showToast without the type parameter while other calls pass 'success'; update
the onSuccess callback to call showToast('질문이 수정되었습니다', 'success') so it matches
the usage elsewhere (referencing showToast in the onSuccess block and the
surrounding queryClient.invalidateQueries with
recruitmentQueryKeys.managedClubQuestions(clubId)).
- Around line 21-47: Both useCreateRecruitment and useUpdateRecruitment
duplicate the same mutationFn and differ only in the success message; refactor
by extracting shared logic into a single hook (e.g., useRecruitmentMutation) or
a helper that encapsulates useMutation with mutationKey:
recruitmentQueryKeys.managedClubRecruitment(clubId) and mutationFn: (data:
ClubRecruitmentRequest) => putClubRecruitment(clubId, data), and accept a mode
or message parameter to set the onSuccess behavior (showToast(...) and
navigate(-1)); update call sites to use the new unified hook and remove the two
duplicated hooks (keep references to useCreateRecruitment/useUpdateRecruitment
only if you adapt them to wrap the new hook).
In `@src/pages/Manager/ManagedAccount/index.tsx`:
- Around line 24-29: The isFormValid check should explicitly validate amount as
a numeric value instead of relying on Number(amount) which can yield NaN;
replace the current logic around isFormValid to parse the trimmed amount (e.g.,
const parsed = parseFloat(amount.trim())), ensure parsed is finite and > 0, and
keep the existing checks for selectedBankId, accountHolder, and accountNumber
(reference the isFormValid variable and the amount, selectedBankId,
accountHolder, accountNumber identifiers).
- Around line 56-62: The amount input currently accepts any text; restrict and
validate it so only numeric values are stored in the amount state (which maps to
ClubFeeRequest.amount as a string). In the ManagedAccount component change the
input handling for value={amount} and onChange={(e) =>
setAmount(e.target.value)} to validate/normalize the value (for example enforce
type="number" or strip non-digit characters, optionally allow decimal by
permitting one dot) and only call setAmount with the validated string; also add
client-side feedback (e.g., disable submit or show error state) when the value
is empty or invalid so downstream code receiving ClubFeeRequest.amount always
gets a numeric string. Ensure you update the input attributes and the onChange
handler referenced by amount and setAmount accordingly.
In `@src/pages/Manager/ManagedApplicationList/index.tsx`:
- Around line 56-71: The approve/reject buttons use only "O" and "X" which is
not screen-reader friendly; update the button elements rendered in
ManagedApplicationList (the buttons that call handleApprove(e, application.id)
and handleReject(e, application.id)) to include accessible labels—add aria-label
attributes (e.g., aria-label={`Approve application ${application.id}`} and
aria-label={`Reject application ${application.id}`}) and/or visually hidden text
so screen readers convey the action while keeping the current visual "O"/"X" UI
and preserve existing props like disabled and className.
In `@src/pages/Manager/ManagedApplictaionDetail/index.tsx`:
- Around line 36-46: The onError callbacks for approve and reject currently call
showToast with only a message; update both to pass the error toast type as the
second argument (i.e., showToast('요청 처리에 실패했습니다', 'error')). Locate the
approve(...) and reject(...) calls in this file (the onError lambdas that
reference showToast) and change both callbacks to include the 'error' type,
keeping existing onSuccess handlers (closeApprove, closeReject) unchanged.
In `@src/pages/Manager/ManagedClubProfile/index.tsx`:
- Around line 70-91: The submit flow currently calls updateClubInfo without
awaiting it so the finally block (setIsUploading(false)) can run before the
mutation completes; modify handleSubmit to await the updateClubInfo(...) call
(i.e., await updateClubInfo({...})) so the upload/mutation completes before
clearing isUploading, and keep closeSubmitModal(), setIsUploading(true) and the
image upload flow as-is; alternatively, remove the local isUploading state and
rely solely on the mutation's isPending state for button disabling—adjust
setIsUploading usage accordingly if you choose that route.
In `@src/pages/Manager/ManagedMemberList/index.tsx`:
- Around line 211-214: Remove the unused commented-out <img> tags in the
ManagedMemberList component and replace them with the existing avatar fallback
div; specifically delete the commented line that references member.imageUrl (the
commented <img className="h-10 w-10 rounded-full object-cover"
src={member.imageUrl} alt={member.name} />) and any other identical commented
<img> occurrences in this file so only the live JSX (the div using
member.name.charAt(0>) remains; this cleans up dead code without changing
rendering behavior.
In `@src/pages/Manager/ManagedRecruitment/index.tsx`:
- Around line 43-62: The ToggleSwitch onChange handlers call patchSettings
without any error handling; update each onChange for the three ToggleSwitches
(labels "모집공고", "지원서", "회비") to call patchSettings and provide an onError
callback (e.g., onError => showToast / setError state) that surfaces a
user-facing error message when patchSettings fails; reference the ToggleSwitch
components' onChange props and the patchSettings function so the failure path
displays feedback and optionally reverts the toggle UI on error.
In `@src/pages/Manager/ManagedRecruitmentForm/index.tsx`:
- Around line 17-27: The hardcoded default question string inside the initial
state for questions should be extracted to a named constant for maintainability:
create a constant (e.g., DEFAULT_PHONE_QUESTION or DEFAULT_MANAGED_QUESTION) and
replace the literal '지원자의 전화번호를 입력해주세요.' used when
managedClubQuestions.questions.length === 0 with that constant; update any
related types/usages (QuestionItem initialization in the useState initializer,
references to tempId generation with crypto.randomUUID()) so the behavior is
unchanged but the default text is centralized.
In `@src/pages/Manager/ManagedRecruitmentWrite/index.tsx`:
- Around line 34-37: The parseDateDot function currently constructs a Date from
any input and can yield Invalid Date; update parseDateDot to validate the input
string and parsed numeric components before returning a Date: first assert the
input matches /^\d{4}\.\d{1,2}\.\d{1,2}$/ or split and ensure you get exactly
three numeric parts, then convert to numbers and check Number.isFinite for
year/month/day, enforce month in 1..12 and day in 1..31, create the Date(year,
month-1, day) and finally verify the produced date matches the original
year/month/day (date.getFullYear() === year etc.); if validation fails, either
throw a descriptive Error or return null/undefined consistently for the callers
to handle.
In `@src/pages/User/MyPage/components/UserInfoCard.tsx`:
- Around line 102-105: Remove the commented-out image element in UserInfoCard:
delete the commented line that contains <img className="h-12 w-12 rounded-full"
src={myInfo.imageUrl} alt="Member Avatar" /> and keep the active avatar fallback
div that uses myInfo.name.charAt(0); this cleans up dead code while preserving
the existing avatar logic (refer to the myInfo variable and the surrounding div
with className="flex h-12 w-12 ...").
- Around line 44-68: ManagerStats currently calls useGetManagedClubs() and
useMyInfo() inside the component; instead change ManagerStats to receive the
needed data via props (e.g., add props myInfo: ReturnTypeOfUseMyInfo and
managedClubList: ReturnTypeOfUseGetManagedClubs or simply
joinedClubsLength:number) and remove the internal calls to useGetManagedClubs
and useMyInfo; update the parent UserInfoCard to call useMyInfo() and
useGetManagedClubs() once and pass myInfo and managedClubList (or the derived
joinedClubs length) into ManagerStats along with the existing onButtonClick prop
so data flow is explicit and hooks aren’t duplicated.
In `@src/utils/hooks/useSmartBack.ts`:
- Around line 44-62: The /mypage/manager/ branch in useSmartBack.ts (the
pathname.startsWith('/mypage/manager/') block) has lengthy conditional logic;
refactor it to use a route-to-target mapping (route config) instead of many
if/else checks: extract the pathname parsing (parts = pathname.split('/'),
clubId = parts[3]) and replace the cascade that checks parts[4]/parts[5] with a
lookup keyed by route patterns (e.g., 'info', 'recruitment', 'recruitment/:id',
'applications', 'applications/:id', 'members') that returns the corresponding
targetPath (falling back to `/mypage/manager`), and update the code that sets
targetPath in the function that contains this block so it reads from the
mapping.
| lastMessage: string | null; | ||
| lastSentAt: string | null; | ||
| unreadCount: number; | ||
| isMuted: boolean; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 1. Find isMuted usages
echo "=== isMuted usages ==="
rg -n --type=ts --type=tsx '\bisMuted\b' -A 2 -B 2
echo ""
echo "=== getChatRooms references ==="
rg -n --type=ts --type=tsx '\bgetChatRooms\b' -A 3 -B 1
echo ""
echo "=== Check entity.ts file ==="
cat -n src/apis/chat/entity.ts | head -30Repository: BCSDLab/KONECT_FRONT_END
Length of output: 1087
🏁 Script executed:
#!/bin/bash
# 2. Find mapping/transformation logic for Room entity
echo "=== Room entity mapping/transformation ==="
fd -e ts -e tsx | xargs rg -l 'Room|ChatRoom' | head -10
echo ""
echo "=== Check api response handling ==="
rg -n --type=ts --type=tsx 'getChatRooms|RoomResponse|chatRooms' -A 3 | head -50Repository: BCSDLab/KONECT_FRONT_END
Length of output: 562
🏁 Script executed:
#!/bin/bash
# Check API implementation
echo "=== src/apis/chat/index.ts ==="
cat -n src/apis/chat/index.ts
echo ""
echo "=== Check Chat page ==="
wc -l src/pages/Chat/index.tsx
echo ""
echo "=== useChat hook ==="
cat -n src/pages/Chat/hooks/useChat.ts | head -50Repository: BCSDLab/KONECT_FRONT_END
Length of output: 4093
🏁 Script executed:
#!/bin/bash
# Search for room data fetching and transformation
echo "=== Search for room fetching patterns ==="
rg -n 'rooms|Room\[' src/apis/chat/ -A 2 -B 2
echo ""
echo "=== Check if isMuted is mapped from backend ==="
rg -n 'muted|Muted' src/ -t ts --max-count 20Repository: BCSDLab/KONECT_FRONT_END
Length of output: 2906
타입 정의와 실제 사용 패턴의 불일치 수정 필요.
isMuted: boolean은 필수 필드로 정의되어 있지만, ChatHeader.tsx에서는 ?? false 폴백으로 사용하고 Chat/index.tsx에서는 직접 참조합니다. 타입을 선택적(isMuted?: boolean)으로 변경하거나, 백엔드에서 항상 값을 내려주는 것을 보장하고 폴백을 제거하세요. 모든 사용처에서 처리 방식을 통일해야 합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/apis/chat/entity.ts` at line 11, The type for the Chat entity's isMuted
field is currently declared as a required boolean but usage in UI is
inconsistent; make the declaration optional (change isMuted: boolean to
isMuted?: boolean in the Chat entity) and then update all callers (e.g.,
ChatHeader.tsx and Chat/index.tsx) to handle undefined consistently (either keep
the ?? false fallback everywhere or remove fallbacks after making backend always
return a value) so type and runtime usage align; ensure references to isMuted in
components use the agreed pattern and update any lint/typescript errors
accordingly.
| {clubMembers.map((member) => ( | ||
| <div key={member.userId} className="flex items-center gap-3"> | ||
| <img src={member.imageUrl} className="h-8 w-8 rounded-full" /> | ||
| <div className="flex flex-col"> | ||
| <span className="text-sm font-medium">{member.name}</span> | ||
| <span className="text-xs text-gray-400">{member.studentNumber}</span> | ||
| </div> |
There was a problem hiding this comment.
참여자 이미지에 대체 텍스트가 필요합니다.
스크린리더 접근성을 위해 alt를 추가해주세요.
🔧 제안 수정
- <img src={member.imageUrl} className="h-8 w-8 rounded-full" />
+ <img src={member.imageUrl} alt={`${member.name} 프로필`} className="h-8 w-8 rounded-full" />📝 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.
| {clubMembers.map((member) => ( | |
| <div key={member.userId} className="flex items-center gap-3"> | |
| <img src={member.imageUrl} className="h-8 w-8 rounded-full" /> | |
| <div className="flex flex-col"> | |
| <span className="text-sm font-medium">{member.name}</span> | |
| <span className="text-xs text-gray-400">{member.studentNumber}</span> | |
| </div> | |
| {clubMembers.map((member) => ( | |
| <div key={member.userId} className="flex items-center gap-3"> | |
| <img src={member.imageUrl} alt={`${member.name} 프로필`} className="h-8 w-8 rounded-full" /> | |
| <div className="flex flex-col"> | |
| <span className="text-sm font-medium">{member.name}</span> | |
| <span className="text-xs text-gray-400">{member.studentNumber}</span> | |
| </div> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/layout/Header/components/ChatHeader.tsx` around lines 75 - 81,
The participant avatar <img> in ChatHeader (inside the clubMembers.map render)
is missing an alt attribute; update the <img> element in the ChatHeader
component to include a descriptive alt (e.g., member.name or `${member.name}
avatar`) so screen readers get meaningful text, and ensure it still uses
member.imageUrl for src and preserves the existing key and className.
| export const useGetClubSettings = (clubId: number) => { | ||
| return useQuery({ | ||
| queryKey: settingsQueryKeys.clubSettings(clubId), | ||
| queryFn: () => getClubSettings(clubId), | ||
| retry: false, | ||
| }); | ||
| }; | ||
|
|
||
| export const usePatchClubSettings = (clubId: number) => { | ||
| const queryClient = useQueryClient(); | ||
|
|
||
| return useMutation({ | ||
| mutationFn: (data: ClubSettingsPatchRequest) => patchClubSettings(clubId, data), | ||
| onSuccess: (updatedSettings) => { | ||
| queryClient.setQueryData(settingsQueryKeys.clubSettings(clubId), updatedSettings); | ||
| }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
rg -n "useGetClubSettings|usePatchClubSettings" --type ts --type tsxRepository: BCSDLab/KONECT_FRONT_END
Length of output: 95
🏁 Script executed:
rg -n "useGetClubSettings|usePatchClubSettings" -t tsRepository: BCSDLab/KONECT_FRONT_END
Length of output: 656
🏁 Script executed:
cat -n src/pages/Manager/ManagedRecruitment/index.tsx | head -40Repository: BCSDLab/KONECT_FRONT_END
Length of output: 2154
🏁 Script executed:
find src -type f -name "*.tsx" -o -name "*.ts" | xargs rg -l "Manager.*route\|Manager.*Route" | head -5Repository: BCSDLab/KONECT_FRONT_END
Length of output: 50
🏁 Script executed:
rg -n "ManagedRecruitment" --type ts | grep -i routeRepository: BCSDLab/KONECT_FRONT_END
Length of output: 420
🏁 Script executed:
sed -n '75,95p' src/App.tsxRepository: BCSDLab/KONECT_FRONT_END
Length of output: 1215
🏁 Script executed:
sed -n '13,50p' src/pages/Manager/ManagedRecruitment/index.tsxRepository: BCSDLab/KONECT_FRONT_END
Length of output: 1674
🏁 Script executed:
cat -n src/pages/Manager/hooks/useManagedSettings.tsRepository: BCSDLab/KONECT_FRONT_END
Length of output: 1162
useGetClubSettings에서 clubId 유효성 체크를 위한 enabled 가드 추가 필요합니다.
useParams에서 가져온 clubId가 undefined이거나 숫자 형태가 아니면 Number(clubId)는 NaN이 되어, clubs/NaN/settings로 잘못된 요청이 발생합니다. useQuery에 enabled: !Number.isNaN(Number(clubId))와 같은 조건을 추가하거나, 컴포넌트에서 clubId 유효성을 먼저 검증해주세요.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/Manager/hooks/useManagedSettings.ts` around lines 10 - 25, The hook
useGetClubSettings currently calls getClubSettings with a possibly invalid
clubId which can become NaN; add a query enabled guard so the query only runs
when clubId is a valid number (e.g. add enabled: !Number.isNaN(Number(clubId)))
to the useQuery options for useGetClubSettings (reference
settingsQueryKeys.clubSettings and getClubSettings); alternatively, validate
clubId before calling this hook in the parent component so useGetClubSettings
never receives an invalid id.
| function ManagedAccount() { | ||
| const { clubId } = useParams<{ clubId: string }>(); | ||
| const { managedClubFee } = useManagedClubFee(Number(clubId)); | ||
| const clubIdNumber = Number(clubId); |
There was a problem hiding this comment.
clubId가 undefined일 때 NaN 처리 필요
Number(undefined)는 NaN을 반환합니다. 라우트 파라미터가 없을 경우에 대한 가드가 필요합니다.
🛡️ 제안하는 수정
const { clubId } = useParams<{ clubId: string }>();
- const clubIdNumber = Number(clubId);
+ const clubIdNumber = Number(clubId);
+ if (!clubId || Number.isNaN(clubIdNumber)) {
+ throw new Error('Invalid 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 clubIdNumber = Number(clubId); | |
| const { clubId } = useParams<{ clubId: string }>(); | |
| const clubIdNumber = Number(clubId); | |
| if (!clubId || Number.isNaN(clubIdNumber)) { | |
| throw new Error('Invalid clubId'); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/Manager/ManagedAccount/index.tsx` at line 10, clubIdNumber is
computed with Number(clubId) which yields NaN when clubId is undefined; update
the logic around the const clubIdNumber to guard against missing route params by
validating clubId first (e.g., check clubId !== undefined && clubId !== ''),
parse it safely (parseInt/clamp or Number(clubId) only after the check), and
handle the invalid case by returning early or using a default/fallback (e.g.,
redirect, show an error state, or set clubIdNumber = null and guard downstream).
Ensure all references to clubIdNumber (the const) are updated to handle the
possible null/undefined/NaN case.
| function ManagedApplicationList() { | ||
| const params = useParams(); | ||
| const navigate = useNavigate(); | ||
| const clubId = Number(params.clubId); |
There was a problem hiding this comment.
clubId NaN 검증 누락
Number(params.clubId)가 NaN일 수 있습니다. ManagedAccount와 동일하게 검증이 필요합니다.
🤖 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 14,
ManagedApplicationList uses const clubId = Number(params.clubId) without NaN
validation; mirror the ManagedAccount approach by validating params.clubId
(e.g., parse/Number then Number.isNaN check) and handle invalid values the same
way ManagedAccount does (return/redirect/error response). Update the clubId
parsing/validation in index.tsx (the clubId variable derived from params.clubId)
to perform the NaN check and the same fallback/error handling logic as in
ManagedAccount.
| const { clubId } = useParams<{ clubId: string }>(); | ||
| const { data: settings } = useGetClubSettings(Number(clubId)); | ||
| const { mutate: patchSettings } = usePatchClubSettings(Number(clubId)); |
There was a problem hiding this comment.
clubId가 undefined일 때 NaN 전달 가능
useParams에서 clubId가 없을 경우 Number(undefined)는 NaN이 됩니다. API 호출 전 유효성 검증이 필요합니다.
🛡️ 제안된 수정
function ManagedRecruitment() {
const { clubId } = useParams<{ clubId: string }>();
+ const numericClubId = Number(clubId);
+
+ if (!clubId || Number.isNaN(numericClubId)) {
+ return null; // 또는 에러 UI
+ }
+
- const { data: settings } = useGetClubSettings(Number(clubId));
- const { mutate: patchSettings } = usePatchClubSettings(Number(clubId));
+ const { data: settings } = useGetClubSettings(numericClubId);
+ const { mutate: patchSettings } = usePatchClubSettings(numericClubId);📝 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 { mutate: patchSettings } = usePatchClubSettings(Number(clubId)); | |
| const { clubId } = useParams<{ clubId: string }>(); | |
| const numericClubId = Number(clubId); | |
| if (!clubId || Number.isNaN(numericClubId)) { | |
| return null; // 또는 에러 UI | |
| } | |
| const { data: settings } = useGetClubSettings(numericClubId); | |
| const { mutate: patchSettings } = usePatchClubSettings(numericClubId); |
🤖 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 14 - 16,
Validate clubId from useParams before converting to Number and calling the
hooks: compute a parsedId (e.g. const parsedId = clubId ? Number(clubId) :
undefined), check Number.isNaN(parsedId) or parsedId === undefined and
return/redirect/show an error early, and only call useGetClubSettings(parsedId)
and usePatchClubSettings(parsedId) (or call them with parsedId and an enabled
flag) when parsedId is a valid number so you never pass NaN to those hooks.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/pages/Manager/ManagedMemberList/index.tsx (1)
211-214: 반복되는 아바타 컴포넌트 추출 권장이니셜 아바타 패턴이 여러 곳(Lines 211-214, 243-244, 336-338, 388-390)에서 동일하게 사용됩니다. 재사용 가능한
InitialAvatar컴포넌트로 추출하면 유지보수성이 향상됩니다.♻️ 컴포넌트 추출 예시
// components/InitialAvatar.tsx interface InitialAvatarProps { name: string; size?: 'sm' | 'md'; } function InitialAvatar({ name, size = 'md' }: InitialAvatarProps) { const sizeClass = size === 'sm' ? 'h-8 w-8' : 'h-10 w-10'; return ( <div className={`flex ${sizeClass} items-center justify-center rounded-full bg-indigo-100 text-indigo-400`}> {name.charAt(0)} </div> ); }🤖 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 211 - 214, Extract the repeated initial-avatar markup into a reusable InitialAvatar component and replace each inline instance (the usages rendering {member.name.charAt(0)} with the rounded indigo circle) with that component; create InitialAvatar({ name, size?: 'sm'|'md' }) that maps size to the appropriate tailwind classes ('h-8 w-8' for sm, 'h-10 w-10' for md) and renders name.charAt(0), then update all occurrences in ManagedMemberList (the blocks that currently compute member.name.charAt(0) at the four locations) to use <InitialAvatar name={member.name} size="md" /> or size="sm" as needed.
🤖 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/Manager/ManagedMemberList/index.tsx`:
- Around line 548-587: The two Pre-Member modal blocks are duplicated and share
state (isPreMemberActionOpen/isPreMemberDeleteOpen) causing both to render;
remove the redundant modal render and keep a single pair of BottomModal
components that use selectedPreMember, handleOpenPreMemberDelete,
handleDeletePreMember, isDeletingPreMember, isPending and the close handlers
(closePreMemberAction/closePreMemberDelete); ensure the remaining modals
correctly toggle only their respective state (isPreMemberActionOpen and
isPreMemberDeleteOpen) so they cannot be open simultaneously.
- Around line 266-293: The pre-members rendering block is duplicated causing the
preMembersList to show twice; remove the redundant JSX block that maps
preMembersList.preMembers (the duplicate Card list that uses member.preMemberId
as key and calls handlePreMemberAction on button click) so only one rendering of
the preMembers list remains in the ManagedMemberList component (look for the JSX
that references preMembersList.preMembers, member.preMemberId, member.name,
member.studentNumber, and handlePreMemberAction) and ensure any surrounding
conditional (preMembersList.preMembers.length > 0) is preserved.
---
Nitpick comments:
In `@src/pages/Manager/ManagedMemberList/index.tsx`:
- Around line 211-214: Extract the repeated initial-avatar markup into a
reusable InitialAvatar component and replace each inline instance (the usages
rendering {member.name.charAt(0)} with the rounded indigo circle) with that
component; create InitialAvatar({ name, size?: 'sm'|'md' }) that maps size to
the appropriate tailwind classes ('h-8 w-8' for sm, 'h-10 w-10' for md) and
renders name.charAt(0), then update all occurrences in ManagedMemberList (the
blocks that currently compute member.name.charAt(0) at the four locations) to
use <InitialAvatar name={member.name} size="md" /> or size="sm" as needed.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/pages/Manager/ManagedRecruitmentWrite/index.tsx (1)
175-189:⚠️ Potential issue | 🟠 Major훅의 기본
onSuccess를 제거하고 컴포넌트에서 네비게이션 관리
useCreateRecruitment훅 내부에서 이미navigate(-1)을 호출합니다. 컴포넌트에서 전달한onSuccess와 함께 두 번 실행되어 네비게이션이 충돌합니다.훅의
onSuccess를 제거하거나 컴포넌트에서onSuccess전달을 생략하세요:해결 방안
- const onSuccess = () => navigate(`/manager/${clubId}/recruitment`); - if (isAlwaysRecruiting) { - saveRecruitment({ content, images: imageData, isAlwaysRecruiting: true }, { onSuccess }); + saveRecruitment({ content, images: imageData, isAlwaysRecruiting: true }); } else { saveRecruitment( { content, images: imageData, isAlwaysRecruiting: false, startDate: formatDateDot(startDate), endDate: formatDateDot(endDate), }, - { onSuccess } ); }🤖 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 175 - 189, The hook useCreateRecruitment currently performs navigation itself (calls navigate(-1)) which conflicts with the component-provided onSuccess; remove the internal navigation from useCreateRecruitment so it only invokes the success callback, and keep this component's saveRecruitment calls (the saveRecruitment(...) invocations in ManagedRecruitmentWrite/index.tsx) as the single place that handles navigation via the onSuccess callback (navigate(`/manager/${clubId}/recruitment`)).
🧹 Nitpick comments (4)
src/pages/Manager/ManagedMemberList/index.tsx (1)
211-214: 이니셜 표시 로직 중복 → 헬퍼로 정리하면 더 깔끔합니다.공백/빈 문자열 대비도 같이 처리하면 UI가 더 안전합니다.
♻️ 제안 diff
+const getInitial = (name: string) => name.trim().charAt(0) || '?'; + // ...생략 - {member.name.charAt(0)} + {getInitial(member.name)} // ...생략 - {member.name.charAt(0)} + {getInitial(member.name)} // ...생략 - {member.name.charAt(0)} + {getInitial(member.name)} // ...생략 - {member.name.charAt(0)} + {getInitial(member.name)}Also applies to: 243-245, 306-309, 358-361
🤖 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 211 - 214, Extract the repeated avatar-initial rendering into a helper (e.g., getInitial(memberName) or renderInitialAvatar) and replace the duplicated blocks at the occurrences referencing member.name (lines around the current blocks at the JSX where the fallback div shows member.name.charAt(0)); implement the helper to trim whitespace, return a safe fallback (e.g., empty string or “?”) for undefined/empty names, and ensure it returns an uppercased single character; update the JSX to call this helper instead of directly using member.name.charAt(0) so all locations (the current block and the other noted occurrences) share the same safe logic.src/pages/Manager/ManagedRecruitmentWrite/index.tsx (3)
146-155: 기존 이미지에 대한 불필요한revokeObjectURL호출
isExisting이미지는 서버 URL이므로URL.revokeObjectURL호출이 불필요합니다.♻️ 제안하는 수정
const handleDeleteImage = () => { - URL.revokeObjectURL(images[currentImageIndex].previewUrl); + const targetImage = images[currentImageIndex]; + if (!targetImage.isExisting) { + URL.revokeObjectURL(targetImage.previewUrl); + } const newImages = images.filter((_, index) => index !== currentImageIndex);🤖 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 146 - 155, In handleDeleteImage, avoid calling URL.revokeObjectURL for images that are existing server URLs: check images[currentImageIndex].isExisting (or equivalent flag on the image object) before calling URL.revokeObjectURL; then remove the image from images and update state with setImages and setCurrentImageIndex as currently implemented, ensuring you guard access to images[currentImageIndex] (e.g., verify images.length and currentImageIndex are valid) before reading properties.
161-194: 이미지 업로드 실패 시 에러 처리 개선 권장
Promise.all실패 시 에러가 unhandled로 남을 수 있습니다. 명시적 catch로 사용자 경험을 개선하세요.♻️ 제안하는 수정
const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setIsUploading(true); try { const newImages = images.filter((img) => img.file); const existingImages = images.filter((img) => img.isExisting); const uploadResults = await Promise.all(newImages.map((img) => uploadImage(img.file!))); // ... rest of logic + } catch { + // useUploadImage의 error 상태가 UI에 표시됨 + return; } finally { setIsUploading(false); } };🤖 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 161 - 194, handleSubmit currently uses Promise.all(newImages.map(uploadImage)) without explicit error handling, so upload failures can bubble as unhandled rejections; wrap the parallel uploads in a try/catch (or use Promise.allSettled and handle failed items) to catch upload errors, call setIsUploading(false) and show an error to the user before returning, and only proceed to build imageData and call saveRecruitment when uploads succeed; specifically modify handleSubmit to catch errors from uploadImage/Promise.all (or inspect Promise.allSettled results), handle or report failures, and ensure navigate/saveRecruitment are only invoked on success.
384-390:disabled상태 스타일 누락버튼이 비활성화될 때 시각적 피드백이 없어 사용자가 클릭 가능 여부를 구분하기 어렵습니다.
♻️ 제안하는 수정
<button type="submit" disabled={isPending || isUploading || !content.trim() || hasDateError} - className="text-h3 bg-primary w-full rounded-xl py-3.5 text-white" + className="text-h3 bg-primary w-full rounded-xl py-3.5 text-white disabled:bg-indigo-200 disabled:cursor-not-allowed" >🤖 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 384 - 390, The submit button in ManagedRecruitmentWrite is missing a visual disabled style; update the button rendering (the <button> with type="submit" and disabled={isPending || isUploading || !content.trim() || hasDateError}) to append conditional classes when disabled (e.g., reduced opacity, cursor-not-allowed and pointer-events-none) and optionally set aria-disabled to the same disabled expression so keyboard/screen-reader users get correct feedback; ensure you only change the className/attributes for the existing button and keep the current disabled prop and label logic intact.
🤖 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/club/entity.ts`:
- Around line 94-107: The ClubApplyResponse interface declares amount, bankName,
accountNumber, and accountHolder as non-nullable strings even though they can be
null when isFeeRequired is false; update the ClubApplyResponse type so those
four fields are nullable (string | null) to match ClubFeeResponse and to align
with AccountInfo.tsx null-coalescing handling—locate the ClubApplyResponse
declaration in src/apis/club/entity.ts and change amount, bankName,
accountNumber, and accountHolder to allow null.
In `@src/pages/Manager/ManagedRecruitmentWrite/index.tsx`:
- Around line 54-55: The code passes Number(clubId) to useGetManagedRecruitments
and useCreateRecruitment without validating clubId, which yields NaN when clubId
is undefined; fix by validating clubId first (e.g., check clubId !== undefined
&& String(clubId).trim() !== '' and Number.isFinite(Number(clubId))) and only
call/use useGetManagedRecruitments and useCreateRecruitment when the parsed
clubId is a valid number (or pass undefined/skip the hooks if they support it),
and handle the invalid case with an early return or disabled state so API calls
are never made with NaN; reference: clubId, useGetManagedRecruitments,
useCreateRecruitment, Number(clubId).
---
Outside diff comments:
In `@src/pages/Manager/ManagedRecruitmentWrite/index.tsx`:
- Around line 175-189: The hook useCreateRecruitment currently performs
navigation itself (calls navigate(-1)) which conflicts with the
component-provided onSuccess; remove the internal navigation from
useCreateRecruitment so it only invokes the success callback, and keep this
component's saveRecruitment calls (the saveRecruitment(...) invocations in
ManagedRecruitmentWrite/index.tsx) as the single place that handles navigation
via the onSuccess callback (navigate(`/manager/${clubId}/recruitment`)).
---
Duplicate comments:
In `@src/pages/Manager/ManagedApplicationList/index.tsx`:
- Line 14: The Number(params.clubId) conversion in ManagedApplicationList can
produce NaN and cause invalid API calls; update the component (where const
clubId = Number(params.clubId) is defined) to validate the result (e.g., check
isNaN(clubId)) and short-circuit before any API requests or rendering (return
null/redirect/show an error) when clubId is not a valid number; apply the same
guard to the analogous conversion in ManagedApplicationDetail so neither
component will call APIs with a NaN clubId.
---
Nitpick comments:
In `@src/pages/Manager/ManagedMemberList/index.tsx`:
- Around line 211-214: Extract the repeated avatar-initial rendering into a
helper (e.g., getInitial(memberName) or renderInitialAvatar) and replace the
duplicated blocks at the occurrences referencing member.name (lines around the
current blocks at the JSX where the fallback div shows member.name.charAt(0));
implement the helper to trim whitespace, return a safe fallback (e.g., empty
string or “?”) for undefined/empty names, and ensure it returns an uppercased
single character; update the JSX to call this helper instead of directly using
member.name.charAt(0) so all locations (the current block and the other noted
occurrences) share the same safe logic.
In `@src/pages/Manager/ManagedRecruitmentWrite/index.tsx`:
- Around line 146-155: In handleDeleteImage, avoid calling URL.revokeObjectURL
for images that are existing server URLs: check
images[currentImageIndex].isExisting (or equivalent flag on the image object)
before calling URL.revokeObjectURL; then remove the image from images and update
state with setImages and setCurrentImageIndex as currently implemented, ensuring
you guard access to images[currentImageIndex] (e.g., verify images.length and
currentImageIndex are valid) before reading properties.
- Around line 161-194: handleSubmit currently uses
Promise.all(newImages.map(uploadImage)) without explicit error handling, so
upload failures can bubble as unhandled rejections; wrap the parallel uploads in
a try/catch (or use Promise.allSettled and handle failed items) to catch upload
errors, call setIsUploading(false) and show an error to the user before
returning, and only proceed to build imageData and call saveRecruitment when
uploads succeed; specifically modify handleSubmit to catch errors from
uploadImage/Promise.all (or inspect Promise.allSettled results), handle or
report failures, and ensure navigate/saveRecruitment are only invoked on
success.
- Around line 384-390: The submit button in ManagedRecruitmentWrite is missing a
visual disabled style; update the button rendering (the <button> with
type="submit" and disabled={isPending || isUploading || !content.trim() ||
hasDateError}) to append conditional classes when disabled (e.g., reduced
opacity, cursor-not-allowed and pointer-events-none) and optionally set
aria-disabled to the same disabled expression so keyboard/screen-reader users
get correct feedback; ensure you only change the className/attributes for the
existing button and keep the current disabled prop and label logic intact.
| export interface ClubApplyResponse { | ||
| bankId?: number; | ||
| amount?: number | string; | ||
| bankName?: string; | ||
| accountNumber?: string; | ||
| accountHolder?: string; | ||
| deadLine?: string; | ||
| amount: string; | ||
| bankName: string; | ||
| accountNumber: string; | ||
| accountHolder: string; | ||
| isFeeRequired: boolean; | ||
| } | ||
|
|
||
| export interface ClubFeeResponse { | ||
| amount: string | null; | ||
| bankName: string | null; | ||
| accountNumber: string | null; | ||
| accountHolder: string | null; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# ClubApplyResponse와 isFeeRequired 사용 패턴 확인
rg -n 'ClubApplyResponse' src --type ts --type tsx -B2 -A5Repository: BCSDLab/KONECT_FRONT_END
Length of output: 95
🏁 Script executed:
# isFeeRequired 조건부 처리 패턴 확인
rg -n 'isFeeRequired' src --type ts --type tsx -B3 -A3Repository: BCSDLab/KONECT_FRONT_END
Length of output: 95
🏁 Script executed:
# 페이지에서 bankName, accountNumber, accountHolder 등 필드 접근 패턴
rg -n '\.bankName|\.accountNumber|\.accountHolder|\.amount' src/pages --type ts --type tsx -B2 -A2Repository: BCSDLab/KONECT_FRONT_END
Length of output: 95
🏁 Script executed:
# entity.ts 전체 구조 확인
cat -n src/apis/club/entity.ts | head -250Repository: BCSDLab/KONECT_FRONT_END
Length of output: 6719
🏁 Script executed:
rg -n 'ClubApplyResponse' src -B2 -A5Repository: BCSDLab/KONECT_FRONT_END
Length of output: 1302
🏁 Script executed:
rg -n 'isFeeRequired' src -B3 -A3Repository: BCSDLab/KONECT_FRONT_END
Length of output: 4409
🏁 Script executed:
# 회비 필드 접근 패턴 확인
rg -n 'bankName|accountNumber|accountHolder' src -B1 -A1Repository: BCSDLab/KONECT_FRONT_END
Length of output: 4919
ClubApplyResponse 회비 필드를 nullable로 수정하세요.
ClubApplyResponse의 amount, bankName, accountNumber, accountHolder는 isFeeRequired가 false일 때 실제로 null이 되지만 타입에선 필수로 강제되어 있습니다. AccountInfo.tsx에서 이미 ?? null coalescing을 사용해 null 가능성을 처리하고 있으며, ClubFeeResponse와도 동일한 필드를 nullable로 정의하고 있으므로 일관성을 위해 아래처럼 수정이 필요합니다:
export interface ClubApplyResponse {
- amount: string;
- bankName: string;
- accountNumber: string;
- accountHolder: string;
+ amount: string | null;
+ bankName: string | null;
+ accountNumber: string | null;
+ accountHolder: string | null;
isFeeRequired: boolean;
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/apis/club/entity.ts` around lines 94 - 107, The ClubApplyResponse
interface declares amount, bankName, accountNumber, and accountHolder as
non-nullable strings even though they can be null when isFeeRequired is false;
update the ClubApplyResponse type so those four fields are nullable (string |
null) to match ClubFeeResponse and to align with AccountInfo.tsx null-coalescing
handling—locate the ClubApplyResponse declaration in src/apis/club/entity.ts and
change amount, bankName, accountNumber, and accountHolder to allow null.
| const { data: existingRecruitment } = useGetManagedRecruitments(Number(clubId)); | ||
| const { mutate: saveRecruitment, isPending, error } = useCreateRecruitment(Number(clubId)); |
There was a problem hiding this comment.
clubId 유효성 검증 누락
clubId가 undefined일 경우 Number(clubId)는 NaN을 반환합니다. API 호출 전 유효성 검사가 필요합니다.
🛠️ 제안하는 수정
function ManagedRecruitmentWrite() {
const { clubId } = useParams<{ clubId: string }>();
const navigate = useNavigate();
+
+ const parsedClubId = Number(clubId);
+ if (!clubId || Number.isNaN(parsedClubId)) {
+ return <div>잘못된 접근입니다.</div>;
+ }
+
// ... state declarations ...
- const { data: existingRecruitment } = useGetManagedRecruitments(Number(clubId));
- const { mutate: saveRecruitment, isPending, error } = useCreateRecruitment(Number(clubId));
+ const { data: existingRecruitment } = useGetManagedRecruitments(parsedClubId);
+ const { mutate: saveRecruitment, isPending, error } = useCreateRecruitment(parsedClubId);🤖 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 54 - 55,
The code passes Number(clubId) to useGetManagedRecruitments and
useCreateRecruitment without validating clubId, which yields NaN when clubId is
undefined; fix by validating clubId first (e.g., check clubId !== undefined &&
String(clubId).trim() !== '' and Number.isFinite(Number(clubId))) and only
call/use useGetManagedRecruitments and useCreateRecruitment when the parsed
clubId is a valid number (or pass undefined/skip the hooks if they support it),
and handle the invalid case with an early return or disabled state so API calls
are never made with NaN; reference: clubId, useGetManagedRecruitments,
useCreateRecruitment, Number(clubId).
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/Manager/ManagedAccount/index.tsx`:
- Around line 24-35: isFormValid uses amount.trim() but the handleSubmit payload
sends the untrimmed amount; update handleSubmit so the ClubFeeRequest payload
uses amount.trim() (and keep accountNumber.trim() and accountHolder.trim() as
already done) to ensure consistent normalization; locate the isFormValid
constant and the handleSubmit function and replace the payload.amount assignment
with the trimmed value.
---
Duplicate comments:
In `@src/pages/Manager/ManagedAccount/index.tsx`:
- Around line 9-13: The component currently converts useParams()'s clubId to a
number and passes it into hooks (useManagedClubFee, useManagedClubFeeMutation)
without guarding against a missing/invalid clubId; add a validation guard right
after const { clubId } = useParams<{ clubId: string }>() to check if clubId is
present and a valid numeric string, and if not return early (render an
error/placeholder) or set an error state so you do not call
useManagedClubFee(clubIdNumber) or useManagedClubFeeMutation(clubIdNumber) with
NaN; update references to clubIdNumber, useManagedClubFee, and
useManagedClubFeeMutation accordingly so all hooks only run when the validated
numeric clubId exists.
| const isFormValid = | ||
| amount.trim() !== '' && selectedBankId !== null && accountHolder.trim() !== '' && accountNumber.trim() !== ''; | ||
|
|
||
| const handleSubmit = () => { | ||
| const payload: ClubFeeRequest = isFeeEnabled | ||
| ? { | ||
| bankId: selectedBankId, | ||
| amount, | ||
| bankName: selectedBank, | ||
| accountNumber, | ||
| accountHolder, | ||
| deadLine, | ||
| } | ||
| : { | ||
| bankId: null, | ||
| amount: 0, | ||
| bankName: '', | ||
| accountNumber: '', | ||
| accountHolder: '', | ||
| deadLine: '', | ||
| }; | ||
| if (isPending || !isFormValid || selectedBankId === null) return; | ||
|
|
||
| const payload: ClubFeeRequest = { | ||
| amount: amount, | ||
| bankId: selectedBankId, | ||
| accountNumber: accountNumber.trim(), | ||
| accountHolder: accountHolder.trim(), | ||
| }; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n src/pages/Manager/ManagedAccount/index.tsx | head -50Repository: BCSDLab/KONECT_FRONT_END
Length of output: 2626
🏁 Script executed:
# Search for ClubFeeRequest definition
rg -n "ClubFeeRequest" --type ts --type tsx -A 5Repository: BCSDLab/KONECT_FRONT_END
Length of output: 95
🏁 Script executed:
# Check for amount validation/normalization patterns in the file
rg -n "amount" src/pages/Manager/ManagedAccount/index.tsx -B 2 -A 2Repository: BCSDLab/KONECT_FRONT_END
Length of output: 1174
🏁 Script executed:
# Check if amount is used elsewhere and how it's handled
rg -n "amount" src --type ts --type tsx | grep -i "trim\|parse\|normalize\|validate"Repository: BCSDLab/KONECT_FRONT_END
Length of output: 95
🏁 Script executed:
# Find ClubFeeRequest type definition
find . -path "*/apis/club/entity*" -type f | head -20Repository: BCSDLab/KONECT_FRONT_END
Length of output: 93
🏁 Script executed:
# Search for ClubFeeRequest definition with broader pattern
rg -n "ClubFeeRequest" --type ts -A 5 -B 2Repository: BCSDLab/KONECT_FRONT_END
Length of output: 4016
🏁 Script executed:
# Check if amount is expected to be numeric
rg -n "amount.*number|parseFloat|parseInt" src/pages/Manager --type ts -B 2 -A 2Repository: BCSDLab/KONECT_FRONT_END
Length of output: 50
amount 필드에 trim() 정규화 필요
isFormValid 검증에서는 amount.trim()을 사용하지만, 실제 payload 전송 시 원본 amount을 그대로 보냅니다. accountNumber, accountHolder와의 일관성을 위해 payload에도 trim()을 적용하세요.
수정 제안
const payload: ClubFeeRequest = {
- amount: amount,
+ amount: amount.trim(),
bankId: selectedBankId,
accountNumber: accountNumber.trim(),
accountHolder: accountHolder.trim(),
};📝 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 isFormValid = | |
| amount.trim() !== '' && selectedBankId !== null && accountHolder.trim() !== '' && accountNumber.trim() !== ''; | |
| const handleSubmit = () => { | |
| const payload: ClubFeeRequest = isFeeEnabled | |
| ? { | |
| bankId: selectedBankId, | |
| amount, | |
| bankName: selectedBank, | |
| accountNumber, | |
| accountHolder, | |
| deadLine, | |
| } | |
| : { | |
| bankId: null, | |
| amount: 0, | |
| bankName: '', | |
| accountNumber: '', | |
| accountHolder: '', | |
| deadLine: '', | |
| }; | |
| if (isPending || !isFormValid || selectedBankId === null) return; | |
| const payload: ClubFeeRequest = { | |
| amount: amount, | |
| bankId: selectedBankId, | |
| accountNumber: accountNumber.trim(), | |
| accountHolder: accountHolder.trim(), | |
| }; | |
| const isFormValid = | |
| amount.trim() !== '' && selectedBankId !== null && accountHolder.trim() !== '' && accountNumber.trim() !== ''; | |
| const handleSubmit = () => { | |
| if (isPending || !isFormValid || selectedBankId === null) return; | |
| const payload: ClubFeeRequest = { | |
| amount: amount.trim(), | |
| bankId: selectedBankId, | |
| accountNumber: accountNumber.trim(), | |
| accountHolder: accountHolder.trim(), | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/Manager/ManagedAccount/index.tsx` around lines 24 - 35, isFormValid
uses amount.trim() but the handleSubmit payload sends the untrimmed amount;
update handleSubmit so the ClubFeeRequest payload uses amount.trim() (and keep
accountNumber.trim() and accountHolder.trim() as already done) to ensure
consistent normalization; locate the isFormValid constant and the handleSubmit
function and replace the payload.amount assignment with the trimmed value.
* fix: 가입 버튼 문구 수정 * fix: 검색창 placeholder 수정 * fix: 불필요해진 UI 비활성화 * fix: 모집공고 없을 때 빈 화면 처리 * fix: 무의미한 회비페이지 수정 * feat: 토글 시 자동 페이지 이동 추가
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/pages/Manager/ManagedRecruitmentWrite/index.tsx (1)
178-198:⚠️ Potential issue | 🔴 Critical훅의 자동 네비게이션 제거 필요
useCreateRecruitment훅이onSuccess에서navigate(-1)을 호출하는데, 컴포넌트도 동일한 콜백에 자신의 네비게이션 로직을 전달합니다. TanStack Query는 훅의onSuccess다음 순서로 전달된onSuccess콜백을 실행하므로, 네비게이션이 두 번 발생합니다.훅에서 네비게이션 로직을 제거하세요.
// useManagedRecruitment.ts export const useCreateRecruitment = (clubId: number) => { const { showToast } = useToastContext(); return useMutation({ mutationKey: recruitmentQueryKeys.managedClubRecruitment(clubId), mutationFn: (recruitmentData: ClubRecruitmentRequest) => putClubRecruitment(clubId, recruitmentData), onSuccess: () => { showToast('모집 공고가 생성되었습니다', 'success'); - navigate(-1); }, }); };🤖 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 178 - 198, The hook useCreateRecruitment currently performs navigation (navigate(-1)) inside its internal onSuccess which, combined with the component's onSuccess passed into saveRecruitment, causes double navigation; update useCreateRecruitment to remove any direct navigation calls and instead only invoke the passed-in onSuccess callback (or resolve the mutation promise) so navigation responsibility remains in the component; locate the internal onSuccess handling in useCreateRecruitment and delete/replace the navigate(-1) call, ensuring saveRecruitment still accepts and calls the external onSuccess provided by the component.
🧹 Nitpick comments (5)
src/pages/Home/components/SimpleClubCard.tsx (1)
3-31: 주석 처리된 UI/임포트는 제거하거나 플래그로 관리해주세요.주석으로 비활성화된 코드가 남아 있으면 유지보수 시 실제 동작과 괴리가 생기기 쉽습니다. 완전히 제거하거나, 기능 플래그/설정으로 토글하는 방식이 더 안전합니다.
🧹 정리 제안 diff
-import { Link } from 'react-router-dom'; -import type { JoinClub } from '@/apis/club/entity'; -// import WarningIcon from '@/assets/svg/warning.svg'; +import { Link } from 'react-router-dom'; +import type { JoinClub } from '@/apis/club/entity'; @@ - {/* {!club.isFeePaid && ( - <div className="text-cap2 flex items-center rounded-full bg-[`#FFE5E5E5`] px-3 py-1.5 font-semibold text-[`#FF4E4E`]"> - <WarningIcon /> - 납부할 회비가 있어요 - </div> - )} */}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Home/components/SimpleClubCard.tsx` around lines 3 - 31, The file SimpleClubCard contains commented-out import and JSX for a WarningIcon and fee UI; remove the dead/commented code (the import line for WarningIcon and the commented block that checks club.isFeePaid) or replace it with a real feature-flagged implementation: add a prop or config flag to SimpleClubCard (e.g., showFeeWarning) and conditionally render the existing JSX block using that prop instead of leaving it commented, and if you keep the UI, restore and import WarningIcon where used and reference club.isFeePaid in the conditional render inside the SimpleClubCard function.src/pages/Manager/ManagedApplicationList/index.tsx (1)
55-55: Non-null assertion(!) 대신 타입 가드 활용 권장
hasNoRecruitment체크로 early return하기 때문에 실제로는 안전하지만,!사용은 TypeScript의 타입 안전성을 우회합니다.♻️ 타입 가드를 활용한 개선안
- {managedClubApplicationList!.applications.map((application) => ( + {managedClubApplicationList?.applications.map((application) => (또는 early return 후 타입을 좁혀주는 방식:
if (hasNoRecruitment || !managedClubApplicationList) { // early return } // 이후 managedClubApplicationList는 non-null로 추론됨🤖 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 55, Replace the non-null assertion on managedClubApplicationList when calling managedClubApplicationList!.applications.map by narrowing its type instead: ensure the component does an early return or explicit type guard that checks both hasNoRecruitment and managedClubApplicationList (e.g., if (hasNoRecruitment || !managedClubApplicationList) return ...) so subsequent code can safely call managedClubApplicationList.applications.map without using `!`; update references in this component (managedClubApplicationList and hasNoRecruitment) accordingly.src/pages/Manager/hooks/useManagedApplications.ts (1)
54-68: Mutation 실패 시 사용자 피드백 누락
onError핸들러가 없어서 API 호출 실패 시 사용자에게 에러 메시지가 표시되지 않습니다.♻️ onError 핸들러 추가 제안
return useMutation({ mutationFn: (applicationId: number) => postClubApplicationApprove(clubId, applicationId), onSuccess: () => { showToast('지원이 승인되었습니다'); queryClient.invalidateQueries({ queryKey: applicationQueryKeys.managedClubApplications(clubId) }); if (navigateBack) navigate(-1); }, + onError: () => { + showToast('승인 처리에 실패했습니다'); + }, });
useRejectApplication에도 동일하게 적용 필요합니다.Also applies to: 70-84
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Manager/hooks/useManagedApplications.ts` around lines 54 - 68, The mutation hooks useApproveApplication and useRejectApplication currently lack onError handlers so API failures provide no user feedback; update both hooks (the useMutation call in useApproveApplication and the analogous useMutation in useRejectApplication that calls postClubApplicationApprove / postClubApplicationReject) to add an onError handler that calls showToast with a user-friendly error message (including error.message or a fallback), and keep existing success behavior (queryClient.invalidateQueries(applicationQueryKeys.managedClubApplications(clubId)) etc. Ensure the onError receives the mutation error argument and shows the toast so users see failures.src/pages/Manager/ManagedRecruitment/index.tsx (1)
16-17: 로딩 상태 처리 누락
settings가undefined일 때(로딩 중) 토글 조작 시 예상치 못한 동작이 발생할 수 있습니다.isLoading상태를 활용하여 UI를 비활성화하는 것을 권장합니다.수정 제안
- const { data: settings } = useGetClubSettings(Number(clubId)); + const { data: settings, isLoading } = useGetClubSettings(Number(clubId)); const { mutate: patchSettings } = usePatchClubSettings(Number(clubId)); + + if (isLoading) { + return <div className="flex h-full items-center justify-center">로딩 중...</div>; + }🤖 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 16 - 17, The component currently reads settings via useGetClubSettings but doesn't handle the loading state; update the call to destructure isLoading from useGetClubSettings and use that to disable the recruitment toggle UI and prevent user interactions while data is undefined, and also guard the patchSettings invocation (from usePatchClubSettings) so it only runs when settings is defined (or after isLoading is false) to avoid applying patches against undefined state.src/pages/Manager/ManagedRecruitmentWrite/index.tsx (1)
149-158: 기존 이미지 URL에revokeObjectURL호출
isExisting인 이미지는 blob URL이 아니므로revokeObjectURL호출이 불필요합니다.수정 제안
const handleDeleteImage = () => { - URL.revokeObjectURL(images[currentImageIndex].previewUrl); + const currentImage = images[currentImageIndex]; + if (!currentImage.isExisting) { + URL.revokeObjectURL(currentImage.previewUrl); + } const newImages = images.filter((_, index) => index !== currentImageIndex);🤖 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 149 - 158, handleDeleteImage currently always calls URL.revokeObjectURL on images[currentImageIndex].previewUrl even for existing images; update handleDeleteImage to first validate currentImageIndex is within bounds, then only call URL.revokeObjectURL when images[currentImageIndex].isExisting is false (i.e., it's a blob/object URL), then proceed to filter out the image and update setImages and setCurrentImageIndex as before; reference the handleDeleteImage function and the images/currentImageIndex state variables when making this change.
🤖 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/Manager/ManagedAccount/index.tsx`:
- Around line 40-45: The current onSuccess flow calls navigate(-1) twice when
location.state?.enableAfterSave is true (one in the mutate hook's onSuccess and
another in the patchSettings onSuccess), causing double back navigation; to fix,
pick one approach: either remove the navigate(-1) from the outer mutate
onSuccess and let patchSettings({ isFeeEnabled: true }, { onSuccess: () =>
navigate(-1) }) handle navigation, or move the enableAfterSave branch into the
hook so that mutate(...) only triggers patchSettings when appropriate and only
one navigate call exists; update the mutate callback (function mutate(...), the
onSuccess handler) and/or the patchSettings call to ensure only a single
navigate(-1) is executed when location.state?.enableAfterSave is true.
In `@src/pages/Manager/ManagedRecruitmentForm/index.tsx`:
- Around line 16-21: clubId can be undefined so calling Number(clubId) may
produce NaN and then pass invalid IDs into hooks; validate and parse the param
first (e.g., const parsedClubId = clubId ? Number(clubId) : undefined) and if
it's missing/invalid either redirect or render an error/loader instead of using
the hooks directly; because React hooks cannot be called conditionally, move the
hook calls (useManagedClubQuestions, useManagedClubQuestionsMutation,
usePatchClubSettings) into a child component that is only rendered when
parsedClubId is a valid number, or ensure those hooks accept undefined safely
and are invoked with a validated numeric ID.
- Around line 69-75: The hook's onSuccess and the component's patchSettings
onSuccess both call navigate(-1), causing double navigation; modify the
updateQuestions call (from useManagedClubQuestionsMutation) to pass a
skipNavigation flag when location.state?.enableAfterSave is true so the hook
does not perform its internal navigate(-1), and keep the component-level
patchSettings({ isApplicationEnabled: true }, { onSuccess: () => navigate(-1) })
to handle navigation for that flow; update the hook
signature/useManagedClubQuestionsMutation to accept and honor skipNavigation in
its mutation options and only call navigate(-1) when skipNavigation is false.
---
Outside diff comments:
In `@src/pages/Manager/ManagedRecruitmentWrite/index.tsx`:
- Around line 178-198: The hook useCreateRecruitment currently performs
navigation (navigate(-1)) inside its internal onSuccess which, combined with the
component's onSuccess passed into saveRecruitment, causes double navigation;
update useCreateRecruitment to remove any direct navigation calls and instead
only invoke the passed-in onSuccess callback (or resolve the mutation promise)
so navigation responsibility remains in the component; locate the internal
onSuccess handling in useCreateRecruitment and delete/replace the navigate(-1)
call, ensuring saveRecruitment still accepts and calls the external onSuccess
provided by the component.
---
Duplicate comments:
In `@src/pages/Manager/ManagedAccount/index.tsx`:
- Around line 28-39: The payload uses the untrimmed amount while validation
checks amount.trim(); inside handleSubmit (where payload: ClubFeeRequest is
built) assign a trimmed value for amount (use amount.trim()) so the sent payload
matches the validated value — update the payload construction in handleSubmit to
use the trimmed amount while leaving other fields as already trimmed.
- Around line 10-14: The code calls Number(clubId) without guarding for
missing/invalid route params, which can produce NaN; before creating
clubIdNumber and calling useManagedClubFee, validate that clubId exists and is a
valid integer (e.g., check typeof clubId === 'string' and !isNaN(Number(clubId))
or use Number.isInteger after parseInt) and handle the invalid case by early
returning, showing an error state, or navigating to a safe route; update the
logic around useParams/useManagedClubFee (references: useParams, clubId,
clubIdNumber, useManagedClubFee) so the hook is only invoked with a valid
numeric clubId.
In `@src/pages/Manager/ManagedApplicationList/index.tsx`:
- Line 14: Validate and handle the case where Number(params.clubId) produces
NaN: replace the bare const clubId = Number(params.clubId) with a guarded
conversion that checks Number.isNaN(clubId) (or uses a fallback) and
returns/redirects/renders an error state when invalid; update any code that uses
the clubId constant so it only runs when clubId is a valid number (refer to the
clubId constant and params.clubId to locate the change).
In `@src/pages/Manager/ManagedRecruitment/index.tsx`:
- Around line 14-17: clubId from useParams is not validated before being passed
to Number(...) and used in hooks (useGetClubSettings, usePatchClubSettings);
validate that clubId exists and is a valid numeric id (e.g., parseInt/Number and
isNaN check) before calling those hooks or converting it, and handle the invalid
case by early returning/UI fallback or navigating away (useNavigate) to prevent
passing NaN/undefined into the hooks; update the code around useParams,
useGetClubSettings, and usePatchClubSettings to gate their invocation on a
validated numeric clubId.
In `@src/pages/Manager/ManagedRecruitmentWrite/index.tsx`:
- Around line 56-58: The calls to useGetManagedRecruitments,
useCreateRecruitment, and usePatchClubSettings are passing Number(clubId)
directly which yields NaN when clubId is undefined; add a validation/parse step
(e.g., parseInt/Number check or isFinite) and only call these hooks or pass a
valid numeric id when clubId is a valid number, otherwise pass undefined or skip
mutation hooks; update references around useGetManagedRecruitments,
useCreateRecruitment, and usePatchClubSettings to use the validated clubId (or
bail out early) to avoid passing NaN.
---
Nitpick comments:
In `@src/pages/Home/components/SimpleClubCard.tsx`:
- Around line 3-31: The file SimpleClubCard contains commented-out import and
JSX for a WarningIcon and fee UI; remove the dead/commented code (the import
line for WarningIcon and the commented block that checks club.isFeePaid) or
replace it with a real feature-flagged implementation: add a prop or config flag
to SimpleClubCard (e.g., showFeeWarning) and conditionally render the existing
JSX block using that prop instead of leaving it commented, and if you keep the
UI, restore and import WarningIcon where used and reference club.isFeePaid in
the conditional render inside the SimpleClubCard function.
In `@src/pages/Manager/hooks/useManagedApplications.ts`:
- Around line 54-68: The mutation hooks useApproveApplication and
useRejectApplication currently lack onError handlers so API failures provide no
user feedback; update both hooks (the useMutation call in useApproveApplication
and the analogous useMutation in useRejectApplication that calls
postClubApplicationApprove / postClubApplicationReject) to add an onError
handler that calls showToast with a user-friendly error message (including
error.message or a fallback), and keep existing success behavior
(queryClient.invalidateQueries(applicationQueryKeys.managedClubApplications(clubId))
etc. Ensure the onError receives the mutation error argument and shows the toast
so users see failures.
In `@src/pages/Manager/ManagedApplicationList/index.tsx`:
- Line 55: Replace the non-null assertion on managedClubApplicationList when
calling managedClubApplicationList!.applications.map by narrowing its type
instead: ensure the component does an early return or explicit type guard that
checks both hasNoRecruitment and managedClubApplicationList (e.g., if
(hasNoRecruitment || !managedClubApplicationList) return ...) so subsequent code
can safely call managedClubApplicationList.applications.map without using `!`;
update references in this component (managedClubApplicationList and
hasNoRecruitment) accordingly.
In `@src/pages/Manager/ManagedRecruitment/index.tsx`:
- Around line 16-17: The component currently reads settings via
useGetClubSettings but doesn't handle the loading state; update the call to
destructure isLoading from useGetClubSettings and use that to disable the
recruitment toggle UI and prevent user interactions while data is undefined, and
also guard the patchSettings invocation (from usePatchClubSettings) so it only
runs when settings is defined (or after isLoading is false) to avoid applying
patches against undefined state.
In `@src/pages/Manager/ManagedRecruitmentWrite/index.tsx`:
- Around line 149-158: handleDeleteImage currently always calls
URL.revokeObjectURL on images[currentImageIndex].previewUrl even for existing
images; update handleDeleteImage to first validate currentImageIndex is within
bounds, then only call URL.revokeObjectURL when
images[currentImageIndex].isExisting is false (i.e., it's a blob/object URL),
then proceed to filter out the image and update setImages and
setCurrentImageIndex as before; reference the handleDeleteImage function and the
images/currentImageIndex state variables when making this change.
| mutate(payload, { | ||
| onSuccess: () => { | ||
| if (location.state?.enableAfterSave) { | ||
| patchSettings({ isFeeEnabled: true }, { onSuccess: () => navigate(-1) }); | ||
| } | ||
| : { | ||
| bankId: null, | ||
| amount: 0, | ||
| bankName: '', | ||
| accountNumber: '', | ||
| accountHolder: '', | ||
| deadLine: '', | ||
| }; | ||
| mutate(payload); | ||
| }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, let's find the useManagedClubFeeMutation hook
fd -t f "useManagedFee" src/Repository: BCSDLab/KONECT_FRONT_END
Length of output: 108
🏁 Script executed:
#!/bin/bash
# Search for useManagedClubFeeMutation definition
rg -n "useManagedClubFeeMutation" src/ -A 10Repository: BCSDLab/KONECT_FRONT_END
Length of output: 3212
🏁 Script executed:
#!/bin/bash
# Look at the full context of the file mentioned in the review
cat -n src/pages/Manager/ManagedAccount/index.tsx | head -100Repository: BCSDLab/KONECT_FRONT_END
Length of output: 5009
🏁 Script executed:
#!/bin/bash
# Check the full useManagedFee.ts file to see complete hook implementation
wc -l src/pages/Manager/hooks/useManagedFee.tsRepository: BCSDLab/KONECT_FRONT_END
Length of output: 111
🏁 Script executed:
#!/bin/bash
# Read the complete useManagedClubFeeMutation implementation
cat -n src/pages/Manager/hooks/useManagedFee.tsRepository: BCSDLab/KONECT_FRONT_END
Length of output: 1797
회비 수정 후 이중 뒤로가기 실행 방지 필요
enableAfterSave 시 hook의 onSuccess(line 42)에서 navigate(-1)을 호출한 후, 커스텀 onSuccess 콜백(line 43)을 통해 patchSettings가 또 다시 navigate(-1)을 실행합니다. 결과적으로 뒤로가기가 두 번 연속 실행되어 예상치 못한 페이지 이동이 발생합니다.
흐름을 다음 중 하나로 통일하세요:
- Hook의
onSuccess에서navigate제거하고, 필요한 곳에서만 네비게이션 수행 enableAfterSave조건 분기를 hook 내부로 옮기기
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/Manager/ManagedAccount/index.tsx` around lines 40 - 45, The current
onSuccess flow calls navigate(-1) twice when location.state?.enableAfterSave is
true (one in the mutate hook's onSuccess and another in the patchSettings
onSuccess), causing double back navigation; to fix, pick one approach: either
remove the navigate(-1) from the outer mutate onSuccess and let patchSettings({
isFeeEnabled: true }, { onSuccess: () => navigate(-1) }) handle navigation, or
move the enableAfterSave branch into the hook so that mutate(...) only triggers
patchSettings when appropriate and only one navigate call exists; update the
mutate callback (function mutate(...), the onSuccess handler) and/or the
patchSettings call to ensure only a single navigate(-1) is executed when
location.state?.enableAfterSave is true.
| const { clubId } = useParams<{ clubId: string }>(); | ||
| const navigate = useNavigate(); | ||
| const location = useLocation(); | ||
| const { managedClubQuestions } = useManagedClubQuestions(Number(clubId)); | ||
| const { mutate: updateQuestions, isPending, error } = useManagedClubQuestionsMutation(Number(clubId)); | ||
| const { mutate: patchSettings } = usePatchClubSettings(Number(clubId)); |
There was a problem hiding this comment.
clubId 유효성 검증 필요
Number(clubId)가 undefined일 경우 NaN이 됩니다. 훅 호출 전 검증이 필요합니다.
수정 제안
function ManagedRecruitmentForm() {
const { clubId } = useParams<{ clubId: string }>();
+ const numericClubId = Number(clubId);
+
+ if (!clubId || Number.isNaN(numericClubId)) {
+ return <div>잘못된 접근입니다.</div>;
+ }
+
const navigate = useNavigate();
const location = useLocation();
- const { managedClubQuestions } = useManagedClubQuestions(Number(clubId));
- const { mutate: updateQuestions, isPending, error } = useManagedClubQuestionsMutation(Number(clubId));
- const { mutate: patchSettings } = usePatchClubSettings(Number(clubId));
+ const { managedClubQuestions } = useManagedClubQuestions(numericClubId);
+ const { mutate: updateQuestions, isPending, error } = useManagedClubQuestionsMutation(numericClubId);
+ const { mutate: patchSettings } = usePatchClubSettings(numericClubId);📝 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 navigate = useNavigate(); | |
| const location = useLocation(); | |
| const { managedClubQuestions } = useManagedClubQuestions(Number(clubId)); | |
| const { mutate: updateQuestions, isPending, error } = useManagedClubQuestionsMutation(Number(clubId)); | |
| const { mutate: patchSettings } = usePatchClubSettings(Number(clubId)); | |
| const { clubId } = useParams<{ clubId: string }>(); | |
| const numericClubId = Number(clubId); | |
| if (!clubId || Number.isNaN(numericClubId)) { | |
| return <div>잘못된 접근입니다.</div>; | |
| } | |
| const navigate = useNavigate(); | |
| const location = useLocation(); | |
| const { managedClubQuestions } = useManagedClubQuestions(numericClubId); | |
| const { mutate: updateQuestions, isPending, error } = useManagedClubQuestionsMutation(numericClubId); | |
| const { mutate: patchSettings } = usePatchClubSettings(numericClubId); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/Manager/ManagedRecruitmentForm/index.tsx` around lines 16 - 21,
clubId can be undefined so calling Number(clubId) may produce NaN and then pass
invalid IDs into hooks; validate and parse the param first (e.g., const
parsedClubId = clubId ? Number(clubId) : undefined) and if it's missing/invalid
either redirect or render an error/loader instead of using the hooks directly;
because React hooks cannot be called conditionally, move the hook calls
(useManagedClubQuestions, useManagedClubQuestionsMutation, usePatchClubSettings)
into a child component that is only rendered when parsedClubId is a valid
number, or ensure those hooks accept undefined safely and are invoked with a
validated numeric ID.
| updateQuestions(requestData, { | ||
| onSuccess: () => { | ||
| if (location.state?.enableAfterSave) { | ||
| patchSettings({ isApplicationEnabled: true }, { onSuccess: () => navigate(-1) }); | ||
| } | ||
| }, | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "*.ts" -o -name "*.tsx" | head -20Repository: BCSDLab/KONECT_FRONT_END
Length of output: 617
🏁 Script executed:
rg -l "useManagedClubQuestionsMutation" --type ts --type tsxRepository: BCSDLab/KONECT_FRONT_END
Length of output: 95
🏁 Script executed:
rg -B 5 -A 20 "export const useManagedClubQuestionsMutation" --type ts --type tsxRepository: BCSDLab/KONECT_FRONT_END
Length of output: 95
🏁 Script executed:
rg "useManagedClubQuestionsMutation" -A 20Repository: BCSDLab/KONECT_FRONT_END
Length of output: 4482
🏁 Script executed:
rg "export const useManagedClubQuestionsMutation" -B 2 -A 25Repository: BCSDLab/KONECT_FRONT_END
Length of output: 1484
🏁 Script executed:
rg -B 10 -A 20 "updateQuestions(requestData" src/pages/Manager/ManagedRecruitmentForm/index.tsxRepository: BCSDLab/KONECT_FRONT_END
Length of output: 161
🏁 Script executed:
cat -n src/pages/Manager/ManagedRecruitmentForm/index.tsx | sed -n '60,85p'Repository: BCSDLab/KONECT_FRONT_END
Length of output: 1088
🏁 Script executed:
rg -B 5 -A 15 "usePatchClubSettings" src/pages/Manager/hooks/useManagedSettings.tsRepository: BCSDLab/KONECT_FRONT_END
Length of output: 489
훅과 컴포넌트에서 이중 네비게이션 발생
useManagedClubQuestionsMutation의 onSuccess에서 navigate(-1)을 실행한 후, enableAfterSave일 때 컴포넌트의 patchSettings onSuccess에서 다시 navigate(-1)을 호출합니다. React Query 뮤테이션은 훅의 onSuccess → 옵션의 onSuccess 순으로 실행되므로, 두 네비게이션이 연달아 발생해 라우팅이 꼬입니다.
훅에 skipNavigation 옵션을 추가하여 enableAfterSave 플로우에서는 훅의 네비게이션을 비활성화하고, 컴포넌트 레벨에서만 제어하도록 수정하세요.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/Manager/ManagedRecruitmentForm/index.tsx` around lines 69 - 75, The
hook's onSuccess and the component's patchSettings onSuccess both call
navigate(-1), causing double navigation; modify the updateQuestions call (from
useManagedClubQuestionsMutation) to pass a skipNavigation flag when
location.state?.enableAfterSave is true so the hook does not perform its
internal navigate(-1), and keep the component-level patchSettings({
isApplicationEnabled: true }, { onSuccess: () => navigate(-1) }) to handle
navigation for that flow; update the hook
signature/useManagedClubQuestionsMutation to accept and honor skipNavigation in
its mutation options and only call navigate(-1) when skipNavigation is false.
* develop → main 배포 (#125) * feat: 알림설정 구현 (#108) * chore: min-w 추가 * [chore] 코드래빗 설정 파일 추가 (#110) * chore: 코드래빗 설정 파일 추가 * chore: 불필요한 tools 설정 제거 * [feat] preMember 목록 분리 및 삭제 기능 추가 (#115) * feat: preMember 목록 분리 및 삭제 기능 추가 * chore: Pascal case * chore: 변경된 변수명 반영 * feat: 학번 숫자 필터링 추가 * [feat] 채팅 목차 알림 상태 추가 (#113) * chore: svg 추가 * feat: 채팅목록 알림 끄기 설정시 UI 추가 * feat: 목차 열릴시 애니메이션 추가 * [feat] 달력 카테고리 분류 추가 (#118) * feat: 카테고리 추가 * chore: 불필요한 코드 삭제 * [refactor] 관리자 페이지 및 마이페이지 UI 수정 및 리팩토링 (#117) * feat: 정보 카드에서 정보 페이지로 이동하도록 기능 추가 * feat: 토스트 전역상태 추가 * refactor: query 훅 분리 및 onSuccess 콜백 옵션 제거 * refactor: query 훅 사용처 수정 * refactor: 사용처 수정 2 * fix: 채팅창 스크롤 초기화 문제 수정 및 줄바꿈 기준 변경 * feat: 토글 스위치 구현 * refactor: 모집 공고 관련 목록 페이지 디자인 수정 * feat: 컴포넌트 구현 및 icon 추가 * refactor: z-index 값 수정 * refactor: API 필드 변경 사항 반영 * refactor: 모집 공고 페이지 디자인 수정 및 라우트 백 수정 * refactor: 학교 목록에 없을 시 문구 디자인 수정 * fix: lint error * fix: 타입 변경 * feat: 모집 관련 페이지 API 추가사항 반영 * refactor: 토스트 타이머 클린업 추라 * refactor: 전역토스트 사용 변경 * refactor: 관리자 클럽 조회 훅 호출 범위 줄이기 * feat: onError handler add * chore: add button type and remove fragment * [feat] 문의하기 버튼 로직 추가 (#120) * feat: API 추가 및 연결 * chore: placeholder 제거 * [fix] 채팅 스크롤 미갱신 버그 및 멤버 직위 렌더링 수정 (#122) * fix: 채팅 스크롤 미작동 수정 * fix: 한글 출력으로 수정 * feat: 첫 글자 보여주도록 수정 (#124) * hotfix: 동아리 소개 줄바꿈 적용 및 한 줄 소개 글자수 제한 상향 * chore: 리뷰 반영 * fix: disabled 조건 변경 * 126 fix 배포 전 qa 사항 반영 (#127) * fix: 가입 버튼 문구 수정 * fix: 검색창 placeholder 수정 * fix: 불필요해진 UI 비활성화 * fix: 모집공고 없을 때 빈 화면 처리 * fix: 무의미한 회비페이지 수정 * feat: 토글 시 자동 페이지 이동 추가 * fix: 페이지 이동 조건 수정 --------- Co-authored-by: 김혜준 <114041848+hyejun0228@users.noreply.github.com> Co-authored-by: hyejun <hyejunkkim228@gmail.com> * develop -> main 배포 (#132) * chore: 타입 변경 반영 * [feat] 모집 공고에 시간 추가 및 지원서 정렬 추가 (#131) * feat: 모집 공고에 시작, 종료 시간 추가 * feat: 지원자 목록 정렬 추가 및 시간 표시 * feat: 채팅, 모집 공고 링크 파싱 기능 추가 * feat: 인스타그램 파싱 추가 * fix: prettier error * refactor: 유틸 분리 및 타입 변경 * chore: 타입 변경 반영 * chore: 동일 스타일 통합 및 entity 확장 사용 * chore:ios 토큰 테스트를 위한 로직 추가 * Revert "chore:ios 토큰 테스트를 위한 로직 추가" This reverts commit 98570fc. * hotfix: 모집 공고 textarea 길이 자동 재계산 추가 * feat: 페이지네이션 추가 * feat: 수정 * feat: 페이지네이션 수정 * feat: 디자인 수정 * feat: 로직 수정 * fix: key 수정 * develop-> main (#135) * chore: 타입 변경 반영 * [feat] 모집 공고에 시간 추가 및 지원서 정렬 추가 (#131) * feat: 모집 공고에 시작, 종료 시간 추가 * feat: 지원자 목록 정렬 추가 및 시간 표시 * feat: 채팅, 모집 공고 링크 파싱 기능 추가 * feat: 인스타그램 파싱 추가 * fix: prettier error * refactor: 유틸 분리 및 타입 변경 * chore: 타입 변경 반영 * chore: 동일 스타일 통합 및 entity 확장 사용 * chore:ios 토큰 테스트를 위한 로직 추가 * Revert "chore:ios 토큰 테스트를 위한 로직 추가" This reverts commit 98570fc. * hotfix: 모집 공고 textarea 길이 자동 재계산 추가 * Reapply "chore:ios 토큰 테스트를 위한 로직 추가" This reverts commit 3a56f4a. * chore: ios토큰 * feat: 가이드 페이지 이미지 변경 * chore: 푸시알림 로직 임시 변경 * refactor: 푸시 알림 토큰 로직 브릿지 방식으로 수정 (#134) * refactor: 푸시 알림 토큰 로직 브릿지 방식으로 수정 * fix: try catch 적용 * feat: 페이지네이션 추가 * feat: 수정 * feat: 페이지네이션 수정 * feat: 디자인 수정 * feat: 로직 수정 * fix: key 수정 * chore: 스테이지 용 임시 UI 제거 --------- Co-authored-by: hyejun <hyejunkkim228@gmail.com> * [feat] sentry 모니터링 추가 (#141) (#142) * chore: sentry 의존성 추가 * feat: sentry 세팅 * chore: 워크플로우 수정 및 vite 세팅 * chore: 임시 추가 * refactor: 색상 토큰 파일 분리 * refactor: 타이포그래피 스타일 파일 분리 * refactor: 피그마 타이포그래피 네이밍으로 클래스 정렬 * fix: 리뷰 코멘트 기반 접근성 및 토큰 정리 --------- Co-authored-by: 김혜준 <114041848+hyejun0228@users.noreply.github.com> Co-authored-by: hyejun <hyejunkkim228@gmail.com>
Summary
Summary by CodeRabbit
새로운 기능
개선사항
버그 수정