Conversation
- 검색 시 카테고리 필터 무시하고 전체 동아리에서 검색 - SearchContext에 isSearching 상태 추가 - 검색과 카테고리 필터링 로직 분리
- SnsIcon의 onClick을 SnsLink로 이동하여 이벤트 전파 문제 해결 - 링크 클릭과 이벤트 추적이 독립적으로 동작하도록 개선
- SNS 링크 클릭 시 동아리 이름과 플랫폼 정보 추적
…A-130 [feature] 검색 시 전체동아리 기준으로 검색된다
…t-MOA-127 [feature] sns링크버튼에 믹스패널 이벤트를 추가한다
[fix] 로그아웃 실패 버그
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
|
Warning
|
| 코호트 / 파일 | 변경 요약 |
|---|---|
라우팅 및 네비게이션 frontend/src/App.tsx |
관리자 섹션에 지원자 관리를 위한 두 개의 새로운 경로 추가 |
API 및 데이터 페치 frontend/src/apis/applicants/getClubApplicants.ts |
클럽 지원자 정보를 가져오는 새로운 비동기 함수 추가 |
컨텍스트 및 상태 관리 frontend/src/context/AdminClubContext.tsx, frontend/src/context/SearchContext.tsx |
지원자 데이터와 검색 상태를 관리하기 위한 컨텍스트 확장 |
컴포넌트 및 페이지 frontend/src/pages/AdminPage/tabs/ApplicantsTab/* |
지원자 관리 및 상세 페이지 컴포넌트 추가 |
검색 및 카테고리 기능 frontend/src/components/common/SearchBox/SearchBox.tsx, frontend/src/pages/MainPage/* |
검색 상태 및 카테고리 선택 로직 업데이트 |
타입 정의 frontend/src/types/applicants.ts |
지원자 및 지원 상태를 위한 새로운 타입 및 열거형 추가 |
Sequence Diagram
sequenceDiagram
participant User
participant AdminPage
participant ApplicantsTab
participant ApplicantsContext
participant API
User->>AdminPage: 지원자 관리 탭 접근
AdminPage->>ApplicantsContext: 클럽 ID 요청
ApplicantsContext-->>AdminPage: 클럽 ID 제공
AdminPage->>API: 지원자 데이터 요청
API-->>ApplicantsContext: 지원자 데이터 반환
ApplicantsContext->>ApplicantsTab: 데이터 전달
ApplicantsTab->>User: 지원자 목록 및 요약 표시
추정된 코드 리뷰 노력
🎯 3 (보통) | ⏱️ ~20분
관련 가능성 있는 PR
- [feature] 검색 시 전체동아리 기준으로 검색된다 #616: 검색 상자 및 컨텍스트 수정
- [feature] 동아리 관리자는 지원자를 확인할 수 있다. #612: 지원자 관리 기능 구현
제안된 레이블
📬 API, 🛠Fix
제안된 리뷰어
- oesnuj
- Zepelown
Note
🔌 MCP (Model Context Protocol) integration is now available in Early Access!
Pro users can now connect to remote MCP servers under the Integrations page to get reviews and chat conversations that understand additional development context.
✨ Finishing Touches
- 📝 Generate Docstrings
🧪 Generate unit tests
- 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.
🪧 Tips
Chat
There are 3 ways to chat with CodeRabbit:
- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
I pushed a fix in commit <commit_id>, please review it.Explain this complex logic.Open a follow-up GitHub issue for this discussion.
- Files and specific lines of code (under the "Files changed" tab): Tag
@coderabbitaiin a new review comment at the desired location with your query. Examples:@coderabbitai explain this code block.
- PR comments: Tag
@coderabbitaiin a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.@coderabbitai read src/utils.ts and explain its main purpose.@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
Support
Need help? Create a ticket on our support page for assistance with any issues or questions.
CodeRabbit Commands (Invoked using PR comments)
@coderabbitai pauseto pause the reviews on a PR.@coderabbitai resumeto resume the paused reviews.@coderabbitai reviewto trigger an incremental review. This is useful when automatic reviews are disabled for the repository.@coderabbitai full reviewto do a full review from scratch and review all the files again.@coderabbitai summaryto regenerate the summary of the PR.@coderabbitai generate docstringsto generate docstrings for this PR.@coderabbitai generate sequence diagramto generate a sequence diagram of the changes in this PR.@coderabbitai generate unit teststo generate unit tests for this PR.@coderabbitai resolveresolve all the CodeRabbit review comments.@coderabbitai configurationto show the current CodeRabbit configuration for the repository.@coderabbitai helpto get help.
Other keywords and placeholders
- Add
@coderabbitai ignoreanywhere in the PR description to prevent this PR from being reviewed. - Add
@coderabbitai summaryto generate the high-level summary at a specific location in the PR description. - Add
@coderabbitaianywhere in the PR title to generate the title automatically.
Documentation and Community
- Visit our Documentation for detailed information on how to use CodeRabbit.
- Join our Discord Community to get help, request features, and share feedback.
- Follow us on X/Twitter for updates and announcements.
There was a problem hiding this comment.
Actionable comments posted: 12
🔭 Outside diff range comments (2)
frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx (1)
49-56: 키보드 접근성 보완 필요div에 role='button'만으로는 포커스/키보드 조작이 동작하지 않습니다. tabIndex와 Enter/Space key 핸들링을 추가하세요.
return ( <Styled.ShareButtonContainer onClick={handleShare} role='button' + tabIndex={0} aria-label='카카오톡으로 동아리 정보 공유하기' + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + handleShare(); + } + }} > <img src={KakaoIcon} alt='카카오톡 공유' /> </Styled.ShareButtonContainer> );frontend/src/pages/ClubDetailPage/components/SnsLinkIcons/SnsLinkIcons.tsx (1)
21-36: 알 수 없는 플랫폼/설정 누락에 대한 방어 코드 추가 권장API가 예상치 못한 키를 반환할 경우 config가 undefined가 되어 런타임 에러 위험이 있습니다. 가드로 안전성을 높이세요.
- const config = SNS_CONFIG[platform as SNSPlatform]; - return ( + const key = platform as SNSPlatform; + const config = SNS_CONFIG[key]; + if (!config) return null; + return ( <Styled.SnsLink key={platform} href={url} target='_blank' - rel='noreferrer' + rel='noopener noreferrer' onClick={() => - trackEvent('sns링크 버튼 클릭', { - platform, - clubName, - }) + trackEvent('sns링크 버튼 클릭', { platform: key, clubName }) } > <Styled.SnsIcon src={config.icon} alt={config.label} /> </Styled.SnsLink> );
🧹 Nitpick comments (14)
frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx (2)
11-13: 환경별 베이스 URL 처리 권장도메인을 상수로 하드코딩하면 스테이징/개발 환경에서 공유 링크가 깨질 수 있습니다. 환경 변수 또는 window.location.origin 기반으로 구성하는 것을 권장합니다.
-const MOADONG_BASE_URL = 'https://www.moadong.com/club/'; +const MOADONG_BASE_URL = + (typeof window !== 'undefined' ? window.location.origin : 'https://www.moadong.com') + '/club/';또는
import.meta.env/process.env를 사용해 환경별 값 주입.
19-25: 트래킹 시점 명확화(선택)sendDefault 호출 전에 “클릭 의도”를, 가능하다면 후속 이벤트로 “공유 호출 성공”을 분리해 측정하면 분석에 유리합니다. (Kakao SDK 콜백 미지원 시 사전 트래킹만 적용)
const handleShare = () => { if (!window.Kakao || !window.Kakao.isInitialized()) { alert('카카오 SDK가 아직 준비되지 않았습니다.'); return; } if (!clubDetail) return; + // 클릭 의도 트래킹 + trackEvent('공유하기 버튼 클릭', { clubName: clubDetail.name }); window.Kakao.Share.sendDefault({ objectType: 'feed', content: { title: clubDetail.name, description: clubDetail.description, imageUrl: clubDetail.logo ? clubDetail.logo : DEFAULT_IMAGE_URL, @@ - }); - trackEvent('공유하기 버튼 클릭', { clubName: clubDetail.name }); + });Also applies to: 46-46
frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx (1)
61-75: 검색 초기화 로직 공통화로 중복 제거 제안
SearchBox등에서도 비슷한 초기화가 있을 가능성이 큽니다. 간단한 유틸/커스텀 훅으로 공통화하면 유지보수성이 좋아집니다.예시:
// 공통 유틸 function useResetSearch() { const { setKeyword, setInputValue, setIsSearching } = useSearch(); return () => { setKeyword(''); setInputValue(''); setIsSearching(false); }; } // 사용 const resetSearch = useResetSearch(); ... resetSearch(); setSelectedCategory(category.id);frontend/src/pages/MainPage/MainPage.tsx (1)
27-27: 'all' 하드코딩 상수화 제안여러 곳에서
'all'을 사용하니 의미 있는 상수로 정의해 재사용하면 가독성과 변경 용이성이 좋아집니다.예시(파일 상단 인근에 배치):
const ALL = 'all' as const;그리고 아래처럼 교체:
-const searchCategory = isSearching ? 'all' : selectedCategory; +const searchCategory = isSearching ? ALL : selectedCategory;참고: 같은 파일의
recruitmentStatus,division에서도 동일 상수를 재사용할 수 있습니다.frontend/src/components/common/SearchBox/SearchBox.tsx (2)
28-29: 'all' 매직 스트링 제거 + 빈 검색어 가드 추가 제안
- 'all'은 오타·분산 정의 리스크가 있으니 상수로 관리하세요.
- 빈 검색어일 때 isSearching을 true로 두면 UX가 어색할 수 있습니다. 공백만 입력 시 검색을 패스하는 가드를 권장합니다.
적용 예시(상수 추가):
// frontend/src/constants/category.ts export const CATEGORY_ALL = 'all' as const;이 컴포넌트 적용:
- setSelectedCategory('all'); + setSelectedCategory(CATEGORY_ALL);빈 검색어 가드(참고용, 범위 외 코드 예시):
const handleSearch = () => { const trimmed = inputValue.trim(); if (!trimmed) { setKeyword(''); setIsSearching(false); inputRef.current?.blur(); return; } redirectToHome(); setKeyword(trimmed); setSelectedCategory(CATEGORY_ALL); setIsSearching(true); inputRef.current?.blur(); trackEvent('Search Executed', { inputValue: trimmed, page: location.pathname }); };
62-63: 접근성: 버튼 aria-label과 아이콘 alt 중복 안내버튼에 aria-label='검색'이 있으므로 내부 아이콘 img는 장식용으로 alt=""(또는 role="presentation")로 두는 편이 스크린리더 중복 낭독을 방지합니다.
적용 예시(범위 외 코드 예시):
<img src={SearchIcon} alt="" role="presentation" />frontend/src/context/AdminClubContext.tsx (2)
7-9: setter 타입을 useState 시그니처와 일치시키기일관성을 위해 setter 타입을
React.Dispatch<React.SetStateAction<...>>로 선언하는 것을 권장합니다. (setClubId도 동일하게 적용 고려)interface AdminClubContextType { clubId: string | null; setClubId: (id: string | null) => void; - applicantsData: ApplicantsInfo | null; - setApplicantsData: (data: ApplicantsInfo | null) => void; + applicantsData: ApplicantsInfo | null; + setApplicantsData: React.Dispatch<React.SetStateAction<ApplicantsInfo | null>>; }
24-24: Provider value 메모이제이션으로 불필요 렌더링 감소Context value 객체가 매 렌더마다 새로 생성됩니다. 부모 렌더에 의한 불필요 하위 리렌더를 줄이기 위해 useMemo를 권장합니다.
적용 예시(범위 외 코드 예시):
import { createContext, useContext, useState, useMemo } from 'react'; const value = useMemo( () => ({ clubId, setClubId, applicantsData, setApplicantsData }), [clubId, applicantsData, setClubId, setApplicantsData] ); <AdminClubContext.Provider value={value}> {children} </AdminClubContext.Provider>frontend/src/context/SearchContext.tsx (1)
30-33: Provider value 메모이제이션 제안value 객체를 useMemo로 감싸 불필요한 리렌더를 줄일 수 있습니다.
적용 예시(범위 외 코드 예시):
import { createContext, useState, useContext, ReactNode, useMemo } from 'react'; const value = useMemo( () => ({ keyword, setKeyword, inputValue, setInputValue, isSearching, setIsSearching }), [keyword, inputValue, isSearching] ); <SearchContext.Provider value={value}>{children}</SearchContext.Provider>frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx (2)
49-67: 인라인 스타일의 매직 넘버 제거 권장
top: 25,gap: 12,width: 16등 숫자 리터럴이 직접 노출되어 있습니다. 스타일 상수 또는 theme 값으로 추출하면 디자인 변경 시 수정 범위를 줄일 수 있습니다.
73-76: 읽기 전용 컴포넌트에 빈onChange전달
onChange={() => {}}는 불필요한 함수 객체를 매 렌더마다 생성합니다.QuestionAnswerer가 읽기 전용이라면 prop 자체를 전달하지 않거나, 컴포넌트에 read-only 모드를 지원하도록 수정하는 편이 명시적입니다.frontend/src/types/applicants.ts (1)
28-33:createdAt타입 명확화 제안날짜 문자열을 그대로
string으로 두면 소비 측에서 매번 파싱/포매팅 로직이 중복됩니다. ISO 형식임을 나타내는 타입 별칭(예:type ISODateString = string)을 두거나Date변환을 고려해 보세요.frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx (1)
131-140: 반복되는 날짜 포매팅 로직 분리매 행마다 IIFE로 날짜 포매팅을 수행하면 불필요한 함수 생성이 발생합니다.
formatDate()유틸을 만들어 메모이제이션하거나 라이브러리(date-fns 등)를 사용하는 편이 가독성과 성능에 좋습니다.frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx (1)
127-134: 중첩 삼항 연산자 사용 지양
ApplicantStatusBadge의 배경색 계산이 삼항 연산자를 3단계 중첩해 가독성이 크게 떨어집니다. status-to-color 맵 객체나 함수로 분리해 주세요.- background: ${({ status }) => - status === '서류검토' - ? '#E6F4FB' - : status === '면접예정' - ? '#E6FBF0' - : status === '합격' - ? '#F5F5F5' - : '#eee'}; + background: ${({ status }) => ({ + 서류검토: '#E6F4FB', + 면접예정: '#E6FBF0', + 합격: '#F5F5F5', + }[status] ?? '#eee')};
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (18)
frontend/src/App.tsx(2 hunks)frontend/src/apis/applicants/getClubApplicants.ts(1 hunks)frontend/src/components/common/SearchBox/SearchBox.tsx(4 hunks)frontend/src/context/AdminClubContext.tsx(2 hunks)frontend/src/context/SearchContext.tsx(3 hunks)frontend/src/hooks/queries/applicants/useGetApplicants.ts(1 hunks)frontend/src/pages/AdminPage/auth/PrivateRoute/PrivateRoute.tsx(1 hunks)frontend/src/pages/AdminPage/components/SideBar/SideBar.styles.ts(0 hunks)frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx(2 hunks)frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx(1 hunks)frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx(1 hunks)frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx(1 hunks)frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsx(1 hunks)frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx(1 hunks)frontend/src/pages/ClubDetailPage/components/SnsLinkIcons/SnsLinkIcons.tsx(2 hunks)frontend/src/pages/MainPage/MainPage.tsx(1 hunks)frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx(2 hunks)frontend/src/types/applicants.ts(1 hunks)
💤 Files with no reviewable changes (1)
- frontend/src/pages/AdminPage/components/SideBar/SideBar.styles.ts
🧰 Additional context used
📓 Path-based instructions (2)
frontend/**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (frontend/.cursorrules)
frontend/**/*.{ts,tsx}: Replace magic numbers with named constants for clarity.
Replace complex or nested ternary operators with if/else statements or IIFEs for readability.
Assign complex boolean conditions to named variables.
Use consistent return types for similar functions and hooks.
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle).
Use unique, descriptive names for custom wrappers and functions to avoid ambiguity.
Define constants near related logic or ensure names link them clearly.
Files:
frontend/src/pages/AdminPage/auth/PrivateRoute/PrivateRoute.tsxfrontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsxfrontend/src/apis/applicants/getClubApplicants.tsfrontend/src/pages/AdminPage/components/SideBar/SideBar.tsxfrontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsxfrontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsxfrontend/src/App.tsxfrontend/src/components/common/SearchBox/SearchBox.tsxfrontend/src/hooks/queries/applicants/useGetApplicants.tsfrontend/src/pages/MainPage/MainPage.tsxfrontend/src/context/AdminClubContext.tsxfrontend/src/context/SearchContext.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsxfrontend/src/types/applicants.tsfrontend/src/pages/ClubDetailPage/components/SnsLinkIcons/SnsLinkIcons.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx
frontend/**/*.tsx
📄 CodeRabbit Inference Engine (frontend/.cursorrules)
frontend/**/*.tsx: Abstract complex logic/interactions into dedicated components or higher-order components (HOCs).
Separate significantly different conditional UI/logic into distinct components.
Colocate simple, localized logic or use inline definitions to reduce context switching.
Choose field-level or form-level cohesion based on form requirements.
Break down broad state management into smaller, focused hooks or contexts.
Use component composition instead of props drilling.
Files:
frontend/src/pages/AdminPage/auth/PrivateRoute/PrivateRoute.tsxfrontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsxfrontend/src/pages/AdminPage/components/SideBar/SideBar.tsxfrontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsxfrontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsxfrontend/src/App.tsxfrontend/src/components/common/SearchBox/SearchBox.tsxfrontend/src/pages/MainPage/MainPage.tsxfrontend/src/context/AdminClubContext.tsxfrontend/src/context/SearchContext.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsxfrontend/src/pages/ClubDetailPage/components/SnsLinkIcons/SnsLinkIcons.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx
🧠 Learnings (15)
📚 Learning: 2025-07-19T05:09:10.702Z
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Break down broad state management into smaller, focused hooks or contexts.
Applied to files:
frontend/src/pages/AdminPage/auth/PrivateRoute/PrivateRoute.tsxfrontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsxfrontend/src/App.tsxfrontend/src/components/common/SearchBox/SearchBox.tsxfrontend/src/pages/MainPage/MainPage.tsxfrontend/src/context/AdminClubContext.tsxfrontend/src/context/SearchContext.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx
📚 Learning: 2025-03-19T05:18:07.818Z
Learnt from: seongwon030
PR: Moadong/moadong#195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.
Applied to files:
frontend/src/pages/AdminPage/auth/PrivateRoute/PrivateRoute.tsxfrontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsxfrontend/src/pages/AdminPage/components/SideBar/SideBar.tsxfrontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsxfrontend/src/hooks/queries/applicants/useGetApplicants.tsfrontend/src/pages/MainPage/MainPage.tsxfrontend/src/context/AdminClubContext.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsxfrontend/src/pages/ClubDetailPage/components/SnsLinkIcons/SnsLinkIcons.tsx
📚 Learning: 2025-07-19T05:09:10.702Z
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.{ts,tsx} : Use consistent return types for similar functions and hooks.
Applied to files:
frontend/src/pages/AdminPage/auth/PrivateRoute/PrivateRoute.tsxfrontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsxfrontend/src/App.tsxfrontend/src/components/common/SearchBox/SearchBox.tsxfrontend/src/hooks/queries/applicants/useGetApplicants.tsfrontend/src/pages/MainPage/MainPage.tsxfrontend/src/context/SearchContext.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsxfrontend/src/types/applicants.ts
📚 Learning: 2025-07-19T05:09:10.702Z
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Colocate simple, localized logic or use inline definitions to reduce context switching.
Applied to files:
frontend/src/pages/AdminPage/auth/PrivateRoute/PrivateRoute.tsxfrontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsxfrontend/src/App.tsxfrontend/src/components/common/SearchBox/SearchBox.tsxfrontend/src/pages/MainPage/MainPage.tsxfrontend/src/context/SearchContext.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx
📚 Learning: 2025-07-19T05:05:10.196Z
Learnt from: seongwon030
PR: Moadong/moadong#548
File: frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx:17-57
Timestamp: 2025-07-19T05:05:10.196Z
Learning: ClubDetailPage.tsx에서 notJoinedClubNames 배열의 하드코딩은 의도적인 설계 결정입니다. 개발자가 명시적으로 하드코딩을 선택했으므로 이에 대한 리팩토링 제안을 하지 않아야 합니다.
Applied to files:
frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsxfrontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsxfrontend/src/pages/ClubDetailPage/components/SnsLinkIcons/SnsLinkIcons.tsx
📚 Learning: 2025-05-19T05:45:52.957Z
Learnt from: lepitaaar
PR: Moadong/moadong#406
File: backend/src/main/java/moadong/club/service/ClubApplyService.java:34-38
Timestamp: 2025-05-19T05:45:52.957Z
Learning: The code duplication between createClubApplication and editClubApplication methods in ClubApplyService.java is acknowledged but will be addressed in a future refactoring, as per the developer's plan.
Applied to files:
frontend/src/apis/applicants/getClubApplicants.ts
📚 Learning: 2025-07-19T05:09:10.702Z
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Separate significantly different conditional UI/logic into distinct components.
Applied to files:
frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsxfrontend/src/App.tsxfrontend/src/components/common/SearchBox/SearchBox.tsxfrontend/src/pages/MainPage/MainPage.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx
📚 Learning: 2025-07-19T05:09:10.702Z
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.{ts,tsx} : Replace magic numbers with named constants for clarity.
Applied to files:
frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsxfrontend/src/App.tsxfrontend/src/pages/MainPage/MainPage.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsxfrontend/src/types/applicants.tsfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx
📚 Learning: 2025-07-19T05:09:10.702Z
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.{ts,tsx} : Define constants near related logic or ensure names link them clearly.
Applied to files:
frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsxfrontend/src/App.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsxfrontend/src/types/applicants.tsfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx
📚 Learning: 2025-07-19T05:09:10.702Z
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Abstract complex logic/interactions into dedicated components or higher-order components (HOCs).
Applied to files:
frontend/src/App.tsxfrontend/src/components/common/SearchBox/SearchBox.tsxfrontend/src/pages/MainPage/MainPage.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx
📚 Learning: 2025-07-19T05:09:10.702Z
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Choose field-level or form-level cohesion based on form requirements.
Applied to files:
frontend/src/App.tsxfrontend/src/components/common/SearchBox/SearchBox.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx
📚 Learning: 2025-07-19T05:09:10.702Z
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Use component composition instead of props drilling.
Applied to files:
frontend/src/components/common/SearchBox/SearchBox.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx
📚 Learning: 2025-07-19T05:09:10.702Z
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.{ts,tsx} : Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle).
Applied to files:
frontend/src/components/common/SearchBox/SearchBox.tsx
📚 Learning: 2025-05-01T08:32:09.398Z
Learnt from: Due-IT
PR: Moadong/moadong#329
File: backend/src/main/java/moadong/club/controller/ClubSearchController.java:0-0
Timestamp: 2025-05-01T08:32:09.398Z
Learning: ClubSearchController의 searchClubsByKeyword 메서드와 ClubSearchService의 searchClubsByKeyword 메서드 사이에 파라미터 순서 일치가 중요하다. 현재 컨트롤러의 매개변수 순서는 'keyword, recruitmentStatus, category, division'인 반면, 서비스 메서드의 순서는 'keyword, recruitmentStatus, division, category'이다. 컨트롤러에서 서비스 호출 시 'keyword, recruitmentStatus, division, category' 순서로 전달하면 category와 division 값이 바뀌어 검색 결과가 잘못될 수 있다.
Applied to files:
frontend/src/pages/MainPage/MainPage.tsx
📚 Learning: 2025-07-20T11:48:50.207Z
Learnt from: seongwon030
PR: Moadong/moadong#541
File: frontend/src/pages/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.tsx:41-43
Timestamp: 2025-07-20T11:48:50.207Z
Learning: moadong 프로젝트는 Next.js가 아닌 순수 React + react-router-dom 기반의 CSR(Client-Side Rendering) SPA이므로, window 객체 사용에 대한 SSR 호환성 문제를 제기하지 않아야 합니다.
Applied to files:
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx
🧬 Code Graph Analysis (6)
frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx (1)
frontend/src/apis/auth/logout.ts (1)
logout(4-13)
frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx (1)
frontend/src/context/SearchContext.tsx (1)
useSearch(39-45)
frontend/src/context/AdminClubContext.tsx (1)
frontend/src/types/applicants.ts (1)
ApplicantsInfo(19-25)
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx (2)
frontend/src/types/applicants.ts (1)
Applicant(27-33)frontend/src/context/AdminClubContext.tsx (1)
useAdminClubContext(30-37)
frontend/src/types/applicants.ts (1)
frontend/src/types/application.ts (1)
AnswerItem(55-58)
frontend/src/pages/ClubDetailPage/components/SnsLinkIcons/SnsLinkIcons.tsx (1)
frontend/src/types/club.ts (1)
SNSPlatform(14-14)
🔇 Additional comments (12)
frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsx (1)
43-48: SNS 트래킹 컨텍스트 보강 적합clubName을 전달해 클릭 이벤트에 컨텍스트를 추가한 점 좋습니다. SnsLinkIcons의 선택적 prop과 일관되고 부작용도 없습니다.
frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx (1)
46-46: 이벤트 트래킹 구조화(LGTM)이벤트명을 고정하고 clubName을 프로퍼티로 분리한 구조가 분석/집계에 유리합니다. 일관성 향상에 동의합니다.
frontend/src/pages/ClubDetailPage/components/SnsLinkIcons/SnsLinkIcons.tsx (1)
12-14: 트래킹 도입(LGTM)clubName을 선택적으로 받아 이벤트 프로퍼티로 전달하는 구조가 확장성과 일관성 측면에서 적절합니다.
frontend/src/App.tsx (2)
20-21: 새 관리자 탭 import 추가는 적절합니다라우팅에 필요한 컴포넌트 import가 잘 연결되었습니다.
81-84: 신규 라우트(/admin/applicants) 연결 LGTM관리자 지원자 목록 탭 진입 라우트가 정상적으로 추가되었습니다.
frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx (1)
61-61: 검색 상태 초기화 추가 LGTM카테고리 클릭 시
isSearching을 false로 전환하는 변경으로 상태 일관성이 좋아졌습니다.Also applies to: 74-74
frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx (1)
17-19: 사이드바 탭 구성 업데이트 LGTM"지원서 관리", "지원자 관리" 탭 추가가 라우팅과 일치합니다.
frontend/src/pages/MainPage/MainPage.tsx (1)
24-28: 검색 중에는 카테고리를 'all'로 강제하는 처리 LGTM
isSearching을 기준으로 카테고리 필터를 덮어쓰는 로직이 명확하고 의도에 부합합니다.Also applies to: 33-34
frontend/src/components/common/SearchBox/SearchBox.tsx (2)
11-12: 컨텍스트 통합 방향 LGTM검색 상태(setIsSearching)와 카테고리 리셋(setSelectedCategory) 연동이 일관적입니다.
47-48: onSubmit 바인딩 확인됨 — 추가 수정 불필요SearchBoxContainer가
styled.form으로 정의되어 있어onSubmit이 폼 요소에 정상적으로 바인딩됩니다.as="form"설정이나 래퍼 교체 없이도 동작하므로 추가 변경이 필요 없습니다.frontend/src/pages/AdminPage/auth/PrivateRoute/PrivateRoute.tsx (1)
11-12: clubId 미존재 시 쿼리 차단 보장현재
clubId ?? ''로 호출합니다. useGetApplicants에enabled: Boolean(clubId)를 추가하면(제안한 변경) clubId가 없을 때 네트워크 호출이 차단되어 안전합니다. 해당 변경이 반영되었는지 확인 바랍니다.검증 포인트:
- useGetApplicants에 enabled가 추가되어 있는가?
- clubId가 falsy일 때 네트워크 호출이 발생하지 않는가?
frontend/src/context/SearchContext.tsx (1)
8-10: 검색 상태 추가 LGTMisSearching과 setter 추가가 다른 구성요소(MainPage, CategoryButtonList)와의 상태 연동에 적절히 부합합니다.
Also applies to: 21-21
| import API_BASE_URL from '@/constants/api'; | ||
| import { secureFetch } from '../auth/secureFetch'; | ||
|
|
||
| const getClubApplicants = async (clubId: string) => { |
There was a problem hiding this comment.
🛠️ Refactor suggestion
반환 타입을 명시하고 도메인 타입을 사용하세요
동일 계층 API 함수들과의 일관성을 위해 명시적 반환 타입을 지정하고, applicants 도메인 타입을 재사용하는 편이 안전합니다.
예시(정확한 타입명은 frontend/src/types/applicants.ts에 맞춰 주세요):
-const getClubApplicants = async (clubId: string) => {
+import type { ApplicantsData } from '@/types/applicants';
+const getClubApplicants = async (clubId: string): Promise<ApplicantsData[]> => {📝 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 getClubApplicants = async (clubId: string) => { | |
| import type { ApplicantsData } from '@/types/applicants'; | |
| const getClubApplicants = async (clubId: string): Promise<ApplicantsData[]> => { |
🤖 Prompt for AI Agents
In frontend/src/apis/applicants/getClubApplicants.ts at line 4, the function
getClubApplicants lacks an explicit return type and does not reuse the
applicants domain type. To fix this, import the appropriate applicants domain
type from frontend/src/types/applicants.ts and specify it as the return type of
the async function to ensure type safety and consistency with other API
functions in the same layer.
| if (!response.ok) { | ||
| console.error(`Failed to fetch: ${response.statusText}`) | ||
| throw new Error((await response.json()).message); | ||
| } | ||
|
|
||
| const result = await response.json(); | ||
| return result.data; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
에러 응답 JSON 파싱 실패 대비 및 메시지 폴백 추가
서버가 비-JSON 응답을 돌려줄 경우 response.json()에서 추가 예외가 나면서 원인 파악이 어려워질 수 있습니다. 폴백 메시지를 두고 파싱 실패를 무시하는 것이 안전합니다.
[security]
- if (!response.ok) {
- console.error(`Failed to fetch: ${response.statusText}`)
- throw new Error((await response.json()).message);
- }
-
- const result = await response.json();
- return result.data;
+ if (!response.ok) {
+ console.error(`Failed to fetch: ${response.status} ${response.statusText}`);
+ let message = `요청 실패: ${response.status} ${response.statusText}`;
+ try {
+ const errBody = await response.json();
+ if (errBody?.message) message = errBody.message;
+ } catch {
+ // ignore JSON parse error
+ }
+ throw new Error(message);
+ }
+
+ const result = await response.json();
+ return result.data;참고: 공용 API 유틸 층에서 콘솔 에러를 직접 찍는 대신(숨은 부작용), 호출 측에서 로깅/토스트를 책임지도록 위임하는 것도 고려해볼 만합니다.
📝 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.
| if (!response.ok) { | |
| console.error(`Failed to fetch: ${response.statusText}`) | |
| throw new Error((await response.json()).message); | |
| } | |
| const result = await response.json(); | |
| return result.data; | |
| if (!response.ok) { | |
| console.error(`Failed to fetch: ${response.status} ${response.statusText}`); | |
| let message = `요청 실패: ${response.status} ${response.statusText}`; | |
| try { | |
| const errBody = await response.json(); | |
| if (errBody?.message) message = errBody.message; | |
| } catch { | |
| // ignore JSON parse error | |
| } | |
| throw new Error(message); | |
| } | |
| const result = await response.json(); | |
| return result.data; |
🤖 Prompt for AI Agents
In frontend/src/apis/applicants/getClubApplicants.ts around lines 7 to 13, the
code calls response.json() directly on error responses, which can throw if the
response is not valid JSON. To fix this, wrap the JSON parsing in a try-catch
block and provide a fallback error message if parsing fails. Also, consider
removing direct console.error calls here and delegate error logging or user
notification to the caller to avoid hidden side effects.
| <Route | ||
| path='applicants/:questionId' | ||
| element={<ApplicantDetailPage />} | ||
| /> |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
URL 파라미터 명 확인 필요: :questionId → :applicantId?
개별 지원자 상세를 보여주는 페이지라면 파라미터명이 questionId보다는 applicantId(또는 유사 도메인명)가 자연스럽습니다. 내부에서 useParams로 어떤 키를 읽는지 확인해 주세요. 불일치 시 상세 페이지가 파라미터를 못 읽습니다.
다음 스크립트로 상세 페이지의 파라미터 사용을 점검할 수 있습니다(레포 루트에서 실행):
수정이 필요하다면:
-<Route
- path='applicants/:questionId'
- element={<ApplicantDetailPage />}
-/>
+<Route
+ path='applicants/:applicantId'
+ element={<ApplicantDetailPage />}
+/>🏁 Script executed:
#!/bin/bash
# ApplicantDetailPage에서 사용하는 useParams 키 확인 및
# 'applicants/' 링크 생성부에서 어떤 파라미터명을 쓰는지 검색
rg -n "useParams|questionId|applicantId" frontend/src/pages/AdminPage/tabs/ApplicantsTab -A 4 -B 2
rg -n "applicants/:" frontend/src -A 2 -B 2
rg -n "to=.*applicants/\\$\\{.*\\}" frontend/src -A 2 -B 2Length of output: 2998
파라미터명 일관성 확인 및 도메인 용어 구체화 제안
현재 Route와 useParams 모두 :questionId / questionId로 일관되게 쓰이고 있어 런타임 이슈는 없으나, 컴포넌트명이 ApplicantDetailPage임을 감안하면 파라미터명은 applicantId가 더 적합합니다. 아래 파일을 중심으로 네이밍을 통일해주세요.
• frontend/src/App.tsx
• frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx
• (링크 생성부) frontend/src/pages/AdminPage/tabs/ApplicantsTab.tsx
예시 diff:
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -85,4 +85,4 @@
- <Route
- path='applicants/:questionId'
- element={<ApplicantDetailPage />}
- />
+ <Route
+ path='applicants/:applicantId'
+ element={<ApplicantDetailPage />}
+ />--- a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx
@@ -14,7 +14,7 @@
- const { questionId } = useParams<{ questionId: string }>();
+ const { applicantId } = useParams<{ applicantId: string }>();
// 지원자 찾기
- const applicant = applicantsData.applicants.find(a => a.id === questionId);
+ const applicant = applicantsData.applicants.find(a => a.id === applicantId);- ApplicantsTab.tsx에서 상세 페이지로 이동하는 링크/
navigate()호출부의 템플릿 리터럴도 동일하게applicants/${applicantId}로 수정 필요합니다.
📝 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.
| <Route | |
| path='applicants/:questionId' | |
| element={<ApplicantDetailPage />} | |
| /> | |
| <Route | |
| - path='applicants/:questionId' | |
| + path='applicants/:applicantId' | |
| element={<ApplicantDetailPage />} | |
| /> |
| <Route | |
| path='applicants/:questionId' | |
| element={<ApplicantDetailPage />} | |
| /> | |
| - const { questionId } = useParams<{ questionId: string }>(); | |
| + const { applicantId } = useParams<{ applicantId: string }>(); | |
| // 지원자 찾기 | |
| - const applicant = applicantsData.applicants.find(a => a.id === questionId); | |
| + const applicant = applicantsData.applicants.find(a => a.id === applicantId); |
🤖 Prompt for AI Agents
In frontend/src/App.tsx lines 85 to 88, rename the route parameter from
:questionId to :applicantId to better reflect the domain and component purpose.
Then, update all related references in
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx and
frontend/src/pages/AdminPage/tabs/ApplicantsTab.tsx, including useParams usage
and link or navigate calls, to consistently use applicantId instead of
questionId. This ensures parameter naming consistency and clarity across routing
and component code.
| export const useGetApplicants = (clubId: string) => { | ||
| return useQuery({ | ||
| queryKey: ['clubApplicants', clubId], | ||
| queryFn: () => getClubApplicants(clubId), | ||
| retry: false, | ||
| }) | ||
| } No newline at end of file |
There was a problem hiding this comment.
빈 clubId로 인한 불필요/오류 요청 방지 및 타입 강화 필요
현재 clubId가 빈 문자열일 때도 쿼리가 실행되어 API 오류 가능성이 있습니다. enabled 가드를 추가하고 반환 타입을 명시하세요.
-import getClubApplicants from "@/apis/applicants/getClubApplicants"
-import { useQuery } from "@tanstack/react-query"
+import getClubApplicants from "@/apis/applicants/getClubApplicants"
+import { useQuery } from "@tanstack/react-query"
+import { ApplicantsInfo } from "@/types/applicants"
-export const useGetApplicants = (clubId: string) => {
- return useQuery({
+export const useGetApplicants = (clubId: string) => {
+ return useQuery<ApplicantsInfo, Error>({
queryKey: ['clubApplicants', clubId],
queryFn: () => getClubApplicants(clubId),
+ enabled: Boolean(clubId),
retry: false,
})
}참고: 기존 호출부(PrivateRoute) 변경 없이 안전해집니다. enabled가 false면 네트워크 호출이 발생하지 않습니다.
📝 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.
| export const useGetApplicants = (clubId: string) => { | |
| return useQuery({ | |
| queryKey: ['clubApplicants', clubId], | |
| queryFn: () => getClubApplicants(clubId), | |
| retry: false, | |
| }) | |
| } | |
| import getClubApplicants from "@/apis/applicants/getClubApplicants" | |
| import { useQuery } from "@tanstack/react-query" | |
| import { ApplicantsInfo } from "@/types/applicants" | |
| export const useGetApplicants = (clubId: string) => { | |
| return useQuery<ApplicantsInfo, Error>({ | |
| queryKey: ['clubApplicants', clubId], | |
| queryFn: () => getClubApplicants(clubId), | |
| enabled: Boolean(clubId), | |
| retry: false, | |
| }) | |
| } |
🤖 Prompt for AI Agents
In frontend/src/hooks/queries/applicants/useGetApplicants.ts around lines 4 to
10, the query runs even when clubId is an empty string, causing unnecessary or
error-prone API calls. Fix this by adding an 'enabled' option to the useQuery
call that disables the query when clubId is empty. Also, explicitly specify the
return type of the hook to strengthen type safety without changing existing call
sites.
| useEffect(() => { | ||
| if (clubId && applicantsData) { | ||
| setApplicantsData(applicantsData); | ||
| } | ||
| }, [clubId, applicantsData]); |
There was a problem hiding this comment.
의존성 누락 및 이전 동아리 데이터 잔존 위험
useEffect의 의존성 배열에 setApplicantsData가 누락되어 있고, clubId 변경 시 이전 동아리의 지원자 데이터가 잠시 남아 있을 수 있습니다. 의존성 보강과 clubId 미존재 시 초기화 로직을 추가하세요.
-useEffect(() => {
- if (clubId && applicantsData) {
- setApplicantsData(applicantsData);
- }
-}, [clubId, applicantsData]);
+useEffect(() => {
+ if (!clubId) {
+ setApplicantsData(null);
+ return;
+ }
+ if (applicantsData) {
+ setApplicantsData(applicantsData);
+ }
+}, [clubId, applicantsData, setApplicantsData]);🤖 Prompt for AI Agents
In frontend/src/pages/AdminPage/auth/PrivateRoute/PrivateRoute.tsx around lines
19 to 23, the useEffect hook is missing setApplicantsData in its dependency
array, and when clubId changes, previous club applicants data may persist
temporarily. Add setApplicantsData to the dependency array and include logic to
clear applicantsData (e.g., set to null or empty) when clubId is not present to
prevent stale data display.
| const { data: formData, isLoading, isError } = useGetApplication(clubId!); | ||
|
|
There was a problem hiding this comment.
clubId! 단언 사용 지양
clubId가 undefined일 가능성을 완전히 배제할 수 없다면 non-null 단언보다는 앞단에서 null 체크 후 조기 리턴하거나 훅 호출 자체를 조건부로 분기해 주세요. 런타임에서 API 호출이 실패할 위험이 있습니다.
🤖 Prompt for AI Agents
In frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx
around lines 20 to 21, avoid using the non-null assertion operator on clubId
when calling useGetApplication. Instead, add a null check for clubId before this
line and return early or conditionally call the hook only if clubId is defined
to prevent potential runtime errors from passing undefined to the hook.
| function applicationStatusMapping(status: Applicant['status']): string { | ||
| switch (status) { | ||
| case 'DRAFT': | ||
| case 'SUBMITTED': | ||
| case 'SCREENING': | ||
| return '서류검토'; | ||
| case 'SCREENING_PASSED': | ||
| case 'INTERVIEW_SCHEDULED': | ||
| case 'INTERVIEW_IN_PROGRESS': | ||
| return '면접예정'; | ||
| case 'INTERVIEW_PASSED': | ||
| case 'OFFERED': | ||
| case 'ACCEPTED': | ||
| return '합격'; | ||
| default: | ||
| return ''; | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
상태 매핑 누락 및 하드코딩
applicationStatusMapping이 탈락·거절 계열( SCREENING_FAILED, INTERVIEW_FAILED, DECLINED, CANCELED_BY_APPLICANT ) 상태를 처리하지 않아 빈 문자열이 반환됩니다. UI 일관성을 위해 모든 enum 값을 포괄하거나 default 라벨을 명시적으로 지정해 주세요.
🤖 Prompt for AI Agents
In frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx lines 7 to
24, the applicationStatusMapping function does not handle rejection-related
statuses like SCREENING_FAILED, INTERVIEW_FAILED, DECLINED, and
CANCELED_BY_APPLICANT, causing it to return an empty string. Update the switch
statement to include these missing statuses with appropriate labels for UI
consistency, or provide a clear default label instead of returning an empty
string.
| (item: Applicant, index: number) => ( | ||
| <Styled.ApplicantTableRow | ||
| key={index} | ||
| style={{ cursor: 'pointer' }} |
There was a problem hiding this comment.
key에 배열 인덱스 사용 금지
테이블 행의 key로 index를 사용하면 정렬/필터 시 리렌더 최적화가 깨집니다. item.id가 고유하므로 이를 key로 사용해 주세요.
🤖 Prompt for AI Agents
In frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx around
lines 104 to 107, the key prop for the table rows is currently set to the array
index, which can cause inefficient re-renders during sorting or filtering.
Replace the key={index} with key={item.id} to use the unique identifier of each
applicant for better React rendering optimization.
| {item.answers[0].value} | ||
| </Styled.ApplicantTableCol> |
There was a problem hiding this comment.
🛠️ Refactor suggestion
이름 추출 로직이 취약합니다
item.answers[0]이 항상 이름 질문을 보장하지 않습니다. 이름을 별도 필드로 받도록 API/타입을 확장하거나, 질문 ID를 상수로 정의해 명시적으로 찾도록 수정하는 편이 안전합니다.
🤖 Prompt for AI Agents
In frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx around
lines 125 to 126, the current code assumes item.answers[0] always contains the
name, which is unreliable. To fix this, update the code to explicitly find the
answer with the name question's ID by defining the question ID as a constant and
searching item.answers for that ID. Alternatively, extend the API and types to
include a dedicated name field and use that instead.
| href={url} | ||
| target='_blank' | ||
| rel='noreferrer' | ||
| onClick={() => |
There was a problem hiding this comment.
🛠️ Refactor suggestion
외부 링크 보안 헤더 보완
target='_blank' 사용 시 'noopener'를 함께 지정해야 opener 취약점을 예방할 수 있습니다.
- rel='noreferrer'
+ rel='noopener noreferrer'Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In frontend/src/pages/ClubDetailPage/components/SnsLinkIcons/SnsLinkIcons.tsx at
line 28, the anchor tags using target='_blank' lack the 'rel="noopener"'
attribute, which is necessary to prevent opener vulnerabilities. Add
rel="noopener" to all anchor elements that have target='_blank' to enhance
security against potential malicious access from the opened page.
#️⃣연관된 이슈
📝작업 내용
지원자의 지원서를 확인 할 수 있습니다.
로그아웃이 되지않는 문제를 해결했습니다.
SNS 버튼 리팩토링
중점적으로 리뷰받고 싶은 부분(선택)
논의하고 싶은 부분(선택)
🫡 참고사항
Summary by CodeRabbit
신규 기능
개선 사항
버그 수정
스타일
기타