Conversation
- validateApplicationForm 함수로 제목/설명 필수 및 최대 길이, 질문 제목, 외부 URL 검증
- 검증 실패 시 항목별 에러 메시지를 alert으로 표시
- FormTitle에 maxLength={50} 추가
- 기존 인라인 URL 검증 로직을 validateApplicationForm으로 통합
…validation-MOA-666 [feature] 지원서 저장 시 검증 추가
- 기존 truthy 체크를 제거하고 memo는 string 타입, status는 ApplicationStatus enum 타입가드로 검증하도록 변경
…unce-MOA-669 [fix] 지원자 수정 디바운스 제거
- applicationData를 tanstack으로 가져옴
…r-MOA-671 [Fix] 지원자 페이지 새로고침 에러 해결
…ade-MOA-673 [chore] Swiper 라이브러리 버전 업그레이드
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning
|
| Cohort / File(s) | Summary |
|---|---|
Swiper 의존성 업그레이드 frontend/package.json |
swiper 버전 ^11.2.10 → ^12.1.2로 업데이트 (파일 끝 개행 제거 포함). |
신청자 상세 및 업데이트 흐름 frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx |
useGetApplicants 도입으로 신청자 리스트 사전 페치 추가, debounce 제거, 메모는 local state에서 onChange로 관리 후 onBlur에서 서버 업데이트 호출, isApplicationStatus 타입 가드 추가, 로딩/에러 처리 분리 및 개선. |
임포트 정리 (Applicants 리스트) frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsx |
사용하지 않는 useQueryClient 및 updateApplicationStatus 등 불필요한 import 제거. |
클라이언트 측 폼 검증 추가 frontend/src/pages/AdminPage/validation/validateApplicationForm.ts, frontend/src/pages/AdminPage/tabs/ApplicationEditTab/ApplicationEditTab.tsx |
새로운 validateApplicationForm·hasErrors 추가(제목/설명/질문/외부URL 검증), ApplicationEditTab에서 제출 전 유효성 검사 도입 및 에러 집계/알림, FormTitle에 maxLength={50} 적용, 기존 하드코딩된 URL 화이트리스트 제거 및 검증 모듈로 이동. |
Sequence Diagram(s)
sequenceDiagram
participant AdminUI as Admin UI (ApplicantDetailPage)
participant Hook as useGetApplicants / useUpdateApplicant
participant API as Applicants API
participant Store as Applicants list (cached)
AdminUI->>Hook: 요청 (컴포넌트 마운트) -> useGetApplicants 쿼리 실행
Hook->>API: GET /applicants
API-->>Hook: applicants 데이터 반환
Hook-->>Store: 캐시 저장
Hook-->>AdminUI: applicantsData 제공
Note over AdminUI,Hook: 사용자 편집 (메모/상태)
AdminUI->>AdminUI: 로컬 상태 업데이트 (onChange)
AdminUI->>Hook: onBlur -> updateApplicantDetail 호출
Hook->>API: PATCH /applicants/:id (변경사항)
API-->>Hook: 업데이트 결과
Hook-->>Store: 캐시 갱신
Hook-->>AdminUI: 업데이트 완료/에러 반환
Estimated code review effort
🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
- [feature] 지원서 저장 시 검증 추가 #1221: 동일한
validateApplicationForm/hasErrors추가 및 ApplicationEditTab의 클라이언트 검증 변경과 직접적 연관. - [fix] 지원자 수정 디바운스 제거 #1223: ApplicantDetailPage에서 debounce 제거, isApplicationStatus 도입, 메모 onBlur 방식 도입과 직접적인 코드 변경 유사성.
- [Fix] 지원자 페이지 새로고침 에러 해결 #1225:
useGetApplicants훅 도입 및 applicants 쿼리 모듈 내보내기와 관련된 변경으로 연관.
Suggested labels
💻 FE, 📈 release, ✨ Feature
Suggested reviewers
- lepitaaar
- oesnuj
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
| Check name | Status | Explanation |
|---|---|---|
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
| Title check | ✅ Passed | 제목은 버전 업그레이드 릴리스를 나타내며, 커밋 메시지와 PR 설명에 따르면 관리자 기능 버그 수정과 swiper 라이브러리 보안 업그레이드를 포함하는 변경사항과 부분적으로 관련이 있습니다. |
| Docstring Coverage | ✅ Passed | No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. |
✏️ Tip: You can configure your own custom pre-merge checks in the settings.
✨ Finishing Touches
- 📝 Generate docstrings (stacked PR)
- 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
- Create PR with unit tests
- Post copyable unit tests in a comment
- Commit unit tests in branch
develop-fe
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands and usage tips.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx (1)
104-110: 에러 가드 순서 —isError를!applicantsData앞으로 이동 권장현재 순서에서는
isApplicantsError가false이고applicantsData가undefined이면서 동시에isError(폼 데이터 오류)가true인 경우, 사용자가 실제 폼 오류 메시지 대신 "지원자 데이터를 불러올 수 없습니다."를 보게 됩니다.isError→!formData검사를!applicantsData앞으로 올리면 더 정확한 오류 메시지를 표시할 수 있습니다.♻️ 수정 제안
if (isLoading || isApplicantsLoading) return <Spinner />; if (isApplicantsError) return <div>지원자 데이터를 불러오는 중 오류가 발생했습니다.</div>; -if (!applicantsData) return <div>지원자 데이터를 불러올 수 없습니다.</div>; if (isError) return <div>지원서 정보를 불러오는 중 오류가 발생했습니다.</div>; if (!formData) return <div>지원서 정보가 없습니다.</div>; +if (!applicantsData) return <div>지원자 데이터를 불러올 수 없습니다.</div>; if (!applicant) return <div>해당 지원자를 찾을 수 없습니다.</div>;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx` around lines 104 - 110, The guard ordering in ApplicantDetailPage causes form-level errors to be shadowed by the applicantsData empty check; move the isError and !formData checks (the conditions using isError and formData) to run before the applicantsData existence check so form-related error messages render instead of "지원자 데이터를 불러올 수 없습니다."; keep isApplicantsError check where it is (the isApplicantsError conditional) and ensure the final order in the render early-return sequence is: isLoading/isApplicantsLoading, isApplicantsError, isError, !formData, !applicantsData, !applicant.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx`:
- Around line 81-99: updateApplicantDetail may send applicantId: undefined
because questionId from useParams can be undefined; add a guard/narrow before
calling updateApplicant (or inside updateApplicantDetail) to ensure questionId
is defined (e.g., if (!questionId) return or surface an error) and then pass the
validated questionId to updateApplicant; reference the questionId variable and
the updateApplicant call in ApplicantDetailPage (and keep existing
isApplicationStatus check).
In
`@frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsx`:
- Line 1: Removing the default React import broke the type reference used in
ApplicantsListTab (the parameter typed as React.MouseEvent at the click handler
around line 92); fix by either re-adding the default import (import React from
'react') at the top of ApplicantsListTab or, preferably, change the type
annotation to the named import form (import { MouseEvent } from 'react') and
update the handler signature to use MouseEvent instead; update the import
statement near the existing useEffect/useState imports and adjust the handler
type (e.g., the function referenced in ApplicantsListTab that accepts e:
React.MouseEvent) accordingly.
In `@frontend/src/pages/AdminPage/validation/validateApplicationForm.ts`:
- Around line 3-9: The ALLOWED_EXTERNAL_URLS array entry for Google Forms is
missing a trailing slash causing startsWith checks to over-accept; update the
'https://docs.google.com/forms' entry in ALLOWED_EXTERNAL_URLS to include the
trailing '/' (i.e. 'https://docs.google.com/forms/') so prefix matching in the
validation logic correctly restricts URLs; verify any code that uses
ALLOWED_EXTERNAL_URLS.startsWith logic still works with the updated constant.
---
Nitpick comments:
In
`@frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx`:
- Around line 104-110: The guard ordering in ApplicantDetailPage causes
form-level errors to be shadowed by the applicantsData empty check; move the
isError and !formData checks (the conditions using isError and formData) to run
before the applicantsData existence check so form-related error messages render
instead of "지원자 데이터를 불러올 수 없습니다."; keep isApplicantsError check where it is (the
isApplicantsError conditional) and ensure the final order in the render
early-return sequence is: isLoading/isApplicantsLoading, isApplicantsError,
isError, !formData, !applicantsData, !applicant.
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx
Show resolved
Hide resolved
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx (1)
74-79:⚠️ Potential issue | 🟠 Major상태 변경 중 메모 입력 내용이 유실될 수 있음
handleStatusChange가 호출되면 즉시updateApplicantDetail이 실행되고, React Query가 뮤테이션 완료 후useGetApplicants쿼리를 재조회합니다. 재조회 결과로 새applicant객체가 반환되면useEffect의setAppMemo(applicant.memo)가 트리거되어 textarea 내용이 뮤테이션 시점의 memo 값으로 초기화됩니다. 사용자가 상태를 변경한 후 blur 이벤트 전까지 메모를 추가로 입력했다면, 그 내용이 조용히 사라집니다.일반적인 해결 방법은 textarea에 포커스가 있는 동안은 외부 데이터로 memo 상태를 덮어쓰지 않도록 보호하는 것입니다.
🛡️ 수정 제안 (isFocused 플래그 활용)
+ const [isMemoFocused, setIsMemoFocused] = useState(false); useEffect(() => { if (applicant) { - setAppMemo(applicant.memo); + if (!isMemoFocused) { + setAppMemo(applicant.memo); + } setApplicantStatus(mapStatusToGroup(applicant.status).status); } - }, [applicant, applicant?.status, applicant?.memo]); + }, [applicant, applicant?.status, applicant?.memo, isMemoFocused]);const handleMemoBlur = () => { + setIsMemoFocused(false); updateApplicantDetail(applicantMemo, applicantStatus); };<Styled.MemoTextarea onChange={handleMemoChange} + onFocus={() => setIsMemoFocused(true)} onBlur={handleMemoBlur} placeholder='메모를 입력해주세요' value={applicantMemo} />Also applies to: 122-130
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx` around lines 74 - 79, The effect that syncs applicant.memo into local state (useEffect in ApplicantDetailPage calling setAppMemo) is overwriting in-progress edits when the textarea is focused; add an isFocused flag for the memo textarea (managed via onFocus/onBlur) and guard the effect so it only calls setAppMemo(applicant.memo) when isFocused is false, leaving local edits intact during focus; update the textarea handlers to toggle isFocused and adjust the effect dependencies (include isFocused) so the memo is only overwritten by incoming applicant updates when the field is not focused; ensure handleStatusChange and the React Query mutation can still update the server state but won't clobber the user's in-flight local edits.
🧹 Nitpick comments (1)
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx (1)
54-59:applicationFormId ?? undefined중복 표현
useParams의 반환 타입이 이미string | undefined이므로?? undefined는 항상applicationFormId자체를 반환합니다. Line 71의useUpdateApplicant호출도 동일합니다.♻️ 수정 제안
- } = useGetApplicants(applicationFormId ?? undefined); + } = useGetApplicants(applicationFormId);- const { mutate: updateApplicant } = useUpdateApplicant( - applicationFormId ?? undefined, - ); + const { mutate: updateApplicant } = useUpdateApplicant(applicationFormId);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx` around lines 54 - 59, The code is passing applicationFormId through a redundant nullish coalescing (applicationFormId ?? undefined) into hooks; since useParams already types applicationFormId as string | undefined, remove the "?? undefined" and pass applicationFormId directly to useGetApplicants and useUpdateApplicant (and any other hooks in this file using the same pattern) to simplify the call sites; look for references to applicationFormId, useGetApplicants, and useUpdateApplicant in ApplicantDetailPage and update them accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In
`@frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx`:
- Around line 74-79: The effect that syncs applicant.memo into local state
(useEffect in ApplicantDetailPage calling setAppMemo) is overwriting in-progress
edits when the textarea is focused; add an isFocused flag for the memo textarea
(managed via onFocus/onBlur) and guard the effect so it only calls
setAppMemo(applicant.memo) when isFocused is false, leaving local edits intact
during focus; update the textarea handlers to toggle isFocused and adjust the
effect dependencies (include isFocused) so the memo is only overwritten by
incoming applicant updates when the field is not focused; ensure
handleStatusChange and the React Query mutation can still update the server
state but won't clobber the user's in-flight local edits.
---
Nitpick comments:
In
`@frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx`:
- Around line 54-59: The code is passing applicationFormId through a redundant
nullish coalescing (applicationFormId ?? undefined) into hooks; since useParams
already types applicationFormId as string | undefined, remove the "?? undefined"
and pass applicationFormId directly to useGetApplicants and useUpdateApplicant
(and any other hooks in this file using the same pattern) to simplify the call
sites; look for references to applicationFormId, useGetApplicants, and
useUpdateApplicant in ApplicantDetailPage and update them accordingly.
#️⃣연관된 이슈
📝작업 내용
중점적으로 리뷰받고 싶은 부분(선택)
논의하고 싶은 부분(선택)
🫡 참고사항
Summary by CodeRabbit
릴리스 노트