Skip to content

Comments

[Release] v1.0.9#627

Merged
lepitaaar merged 20 commits intomainfrom
develop-fe
Aug 9, 2025
Merged

[Release] v1.0.9#627
lepitaaar merged 20 commits intomainfrom
develop-fe

Conversation

@lepitaaar
Copy link
Contributor

@lepitaaar lepitaaar commented Aug 9, 2025

#️⃣연관된 이슈

📝작업 내용

지원자의 지원서를 확인 할 수 있습니다.
로그아웃이 되지않는 문제를 해결했습니다.
SNS 버튼 리팩토링

중점적으로 리뷰받고 싶은 부분(선택)

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요

ex) 메서드 XXX의 이름을 더 잘 짓고 싶은데 혹시 좋은 명칭이 있을까요?

논의하고 싶은 부분(선택)

논의하고 싶은 부분이 있다면 작성해주세요.

🫡 참고사항

Summary by CodeRabbit

  • 신규 기능

    • 관리자 페이지에 지원자 관리 탭 및 지원자 상세 페이지가 추가되었습니다.
    • 지원자 목록, 상태, 메모, 제출일 확인 및 상세 답변 열람이 가능합니다.
    • 지원자 통계 요약 카드와 필터, 검색 기능이 제공됩니다.
  • 개선 사항

    • 검색 시 카테고리 자동 초기화 및 검색 상태 표시 기능이 추가되었습니다.
    • 카테고리 버튼 클릭 시 검색 상태가 초기화됩니다.
    • SNS 링크 클릭 및 공유 버튼 클릭 시 이벤트 트래킹이 개선되었습니다.
  • 버그 수정

    • 관리자 로그아웃 시 refreshToken이 있을 때만 로그아웃이 실행되도록 수정되었습니다.
  • 스타일

    • 지원자 관리 UI에 일관된 스타일이 적용되었습니다.
  • 기타

    • 지원자 관련 타입이 새롭게 정의되었습니다.

lepitaaar and others added 20 commits July 28, 2025 19:01
- 검색 시 카테고리 필터 무시하고 전체 동아리에서 검색
- SearchContext에 isSearching 상태 추가
- 검색과 카테고리 필터링 로직 분리
- SnsIcon의 onClick을 SnsLink로 이동하여 이벤트 전파 문제 해결
- 링크 클릭과 이벤트 추적이 독립적으로 동작하도록 개선
- SNS 링크 클릭 시 동아리 이름과 플랫폼 정보 추적
…A-130

[feature] 검색 시 전체동아리 기준으로 검색된다
…t-MOA-127

[feature] sns링크버튼에 믹스패널 이벤트를 추가한다
[fix] 로그아웃 실패 버그
@lepitaaar lepitaaar self-assigned this Aug 9, 2025
@lepitaaar lepitaaar added 💻 FE Frontend 📈 release 릴리즈 배포 labels Aug 9, 2025
@vercel
Copy link

vercel bot commented Aug 9, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
moadong ✅ Ready (Inspect) Visit Preview 💬 Add feedback Aug 9, 2025 0:28am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 9, 2025

Warning

.coderabbit.yaml has a parsing error

The CodeRabbit configuration file in this repository has a parsing error and default settings were used instead. Please fix the error(s) in the configuration file. You can initialize chat with CodeRabbit to get help with the configuration file.

💥 Parsing errors (1)
Validation error: Invalid regex pattern for base branch. Received: "**" at "reviews.auto_review.base_branches[0]"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

개요

Walkthrough

관리자 섹션에 지원자 관리를 위한 새로운 기능이 추가되었습니다. 지원자 데이터를 가져오고 표시하기 위한 API, 컨텍스트, 훅, 컴포넌트, 타입 등이 구현되었으며, 검색 및 카테고리 기능도 일부 개선되었습니다.

Changes

코호트 / 파일 변경 요약
라우팅 및 네비게이션
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: 지원자 목록 및 요약 표시
Loading

추정된 코드 리뷰 노력

🎯 3 (보통) | ⏱️ ~20분

관련 가능성 있는 PR

제안된 레이블

📬 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.

❤️ Share
🪧 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 @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in 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 pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere 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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 52c1d26 and 5382f19.

📒 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.tsx
  • frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx
  • frontend/src/apis/applicants/getClubApplicants.ts
  • frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx
  • frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx
  • frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsx
  • frontend/src/App.tsx
  • frontend/src/components/common/SearchBox/SearchBox.tsx
  • frontend/src/hooks/queries/applicants/useGetApplicants.ts
  • frontend/src/pages/MainPage/MainPage.tsx
  • frontend/src/context/AdminClubContext.tsx
  • frontend/src/context/SearchContext.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx
  • frontend/src/types/applicants.ts
  • frontend/src/pages/ClubDetailPage/components/SnsLinkIcons/SnsLinkIcons.tsx
  • frontend/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.tsx
  • frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx
  • frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx
  • frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx
  • frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsx
  • frontend/src/App.tsx
  • frontend/src/components/common/SearchBox/SearchBox.tsx
  • frontend/src/pages/MainPage/MainPage.tsx
  • frontend/src/context/AdminClubContext.tsx
  • frontend/src/context/SearchContext.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx
  • frontend/src/pages/ClubDetailPage/components/SnsLinkIcons/SnsLinkIcons.tsx
  • frontend/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.tsx
  • frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx
  • frontend/src/App.tsx
  • frontend/src/components/common/SearchBox/SearchBox.tsx
  • frontend/src/pages/MainPage/MainPage.tsx
  • frontend/src/context/AdminClubContext.tsx
  • frontend/src/context/SearchContext.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx
  • frontend/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.tsx
  • frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx
  • frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx
  • frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsx
  • frontend/src/hooks/queries/applicants/useGetApplicants.ts
  • frontend/src/pages/MainPage/MainPage.tsx
  • frontend/src/context/AdminClubContext.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx
  • frontend/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.tsx
  • frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx
  • frontend/src/App.tsx
  • frontend/src/components/common/SearchBox/SearchBox.tsx
  • frontend/src/hooks/queries/applicants/useGetApplicants.ts
  • frontend/src/pages/MainPage/MainPage.tsx
  • frontend/src/context/SearchContext.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx
  • frontend/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.tsx
  • frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsx
  • frontend/src/App.tsx
  • frontend/src/components/common/SearchBox/SearchBox.tsx
  • frontend/src/pages/MainPage/MainPage.tsx
  • frontend/src/context/SearchContext.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx
  • frontend/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.tsx
  • frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsx
  • frontend/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.tsx
  • frontend/src/App.tsx
  • frontend/src/components/common/SearchBox/SearchBox.tsx
  • frontend/src/pages/MainPage/MainPage.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx
  • frontend/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.tsx
  • frontend/src/App.tsx
  • frontend/src/pages/MainPage/MainPage.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx
  • frontend/src/types/applicants.ts
  • frontend/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.tsx
  • frontend/src/App.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx
  • frontend/src/types/applicants.ts
  • frontend/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.tsx
  • frontend/src/components/common/SearchBox/SearchBox.tsx
  • frontend/src/pages/MainPage/MainPage.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx
  • frontend/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.tsx
  • frontend/src/components/common/SearchBox/SearchBox.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx
  • frontend/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.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx
  • frontend/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: 검색 상태 추가 LGTM

isSearching과 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) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Suggested change
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.

Comment on lines +7 to +13
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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Suggested change
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.

Comment on lines +85 to +88
<Route
path='applicants/:questionId'
element={<ApplicantDetailPage />}
/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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 2

Length of output: 2998


파라미터명 일관성 확인 및 도메인 용어 구체화 제안

현재 RouteuseParams 모두 :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.

Suggested change
<Route
path='applicants/:questionId'
element={<ApplicantDetailPage />}
/>
<Route
- path='applicants/:questionId'
+ path='applicants/:applicantId'
element={<ApplicantDetailPage />}
/>
Suggested change
<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.

Comment on lines +4 to +10
export const useGetApplicants = (clubId: string) => {
return useQuery({
queryKey: ['clubApplicants', clubId],
queryFn: () => getClubApplicants(clubId),
retry: false,
})
} No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

빈 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.

Suggested change
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.

Comment on lines +19 to +23
useEffect(() => {
if (clubId && applicantsData) {
setApplicantsData(applicantsData);
}
}, [clubId, applicantsData]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

의존성 누락 및 이전 동아리 데이터 잔존 위험

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.

Comment on lines +20 to +21
const { data: formData, isLoading, isError } = useGetApplication(clubId!);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

clubId! 단언 사용 지양

clubIdundefined일 가능성을 완전히 배제할 수 없다면 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.

Comment on lines +7 to +24
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 '';
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Comment on lines +104 to +107
(item: Applicant, index: number) => (
<Styled.ApplicantTableRow
key={index}
style={{ cursor: 'pointer' }}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

key에 배열 인덱스 사용 금지

테이블 행의 keyindex를 사용하면 정렬/필터 시 리렌더 최적화가 깨집니다. 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.

Comment on lines +125 to +126
{item.answers[0].value}
</Styled.ApplicantTableCol>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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={() =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

@PororoAndFriends PororoAndFriends self-requested a review August 9, 2025 04:53
Copy link
Collaborator

@PororoAndFriends PororoAndFriends left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니당

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💻 FE Frontend 📈 release 릴리즈 배포

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants