Skip to content

[release] v1.0.6#508

Merged
oesnuj merged 246 commits intomainfrom
develop-fe
Jun 6, 2025
Merged

[release] v1.0.6#508
oesnuj merged 246 commits intomainfrom
develop-fe

Conversation

@oesnuj
Copy link
Member

@oesnuj oesnuj commented Jun 6, 2025

#️⃣ 연관된 이슈

배포 PR

📝 작업 내용

@seongwon030 진짜 수고 많으셨습니다~ 😭 지인짜 많이 했네요 👏
현재 develop-fe 브랜치에서 열려 있는 PR들만 머지되면, 최종 체크리스트 확인 후 이 PR도 머지하면 될 것 같습니다!

📝 지원서 제작 및 답변 시스템 구현

  • 관리자: 동아리 맞춤형 질문 제작 (단답형 / 장문형 / 객관식 등)
  • 사용자: 실시간 답변 가능 (단일/다중 선택 포함)
  • API 연동: 생성 / 조회 / 수정 전부 React Query 기반으로 처리

🎨 UI/UX 개선

  • 공통 Dropdown 컴포넌트 도입
  • 객관식 선택 UX 개선 (선택 시 시각적 피드백 강화)
  • InputField 반응형 스타일 적용 (모바일 최적화)

🛠 관리자 페이지 확장

  • 사이드바에 ‘지원 관리’ 탭 추가
  • 계정 관리 탭 안내 메시지 적용
  • 지원서 기능은 메인 브랜치에서 임시 접근 차단 (곧 오픈 예정)

🔎 검색 UX 개선

  • 동아리 검색 결과 없음 시 안내 메시지 추가
  • 로딩 상태 시 사용자 피드백 강화

🧱 기술적 구조 개선

  • 재사용 가능한 컴포넌트 설계 (QuestionAnswerer, InputField 등)
  • 폴더 및 파일 구조 정리 → 유지보수성 향상

✅ 배포전 체크리스트!!!!

  • Preview Deploy 반드시 확인 (라우팅 및 접근 차단 정상 작동)
  • 사이드바 ‘지원서 관리’ 접근 차단 확인
  • 관련 PR 리뷰 완료 여부 확인
  • 기능 누락 여부 최종 점검

🫡 참고사항

  • 관리자 기능은 develop-fe 브랜치에서 지속 개발 예정입니다
  • 메인 브랜치에서는 안정성을 위한 임시 차단 처리입니다

Summary by CodeRabbit

  • 신규 기능

    • 지원서 폼 생성, 편집, 응답 기능이 추가되었습니다.
    • 지원서 폼 빌더에서 다양한 질문 유형(단답, 장문, 선택 등) 및 필수 여부 설정이 가능합니다.
    • 클럽 상세 페이지에서 지원서 작성이 가능합니다.
    • 커스텀 드롭다운, 커스텀 텍스트에어리어 등 신규 UI 컴포넌트가 추가되었습니다.
    • 지원서 관련 API 연동 및 상태 관리 훅이 추가되었습니다.
    • MSW(Mock Service Worker)를 활용한 프론트엔드 API 목업 환경이 구축되었습니다.
    • SDK(Mixpanel, ChannelService, Sentry) 초기화 로직이 별도 모듈로 분리되어 관리됩니다.
  • 버그 수정

    • 일부 컴포넌트에서 불필요한 React import가 제거되었습니다.
  • 스타일

    • 지원서, 메인, 클럽 상세 등 다양한 컴포넌트에 스타일 및 반응형 UI가 개선되었습니다.
    • textarea의 글로벌 폰트 적용 등 전역 스타일이 보완되었습니다.
  • 테스트

    • 지원서 API 및 유틸리티 함수에 대한 테스트가 추가되었습니다.
  • 문서화

    • 타입 및 상수 정의가 추가되어 개발자 가독성이 향상되었습니다.
  • 기타

    • 클럽 정보 편집 탭에서 SNS 링크 입력 기능이 활성화되었습니다.

seongwon030 and others added 30 commits May 11, 2025 01:30
- 클라이언트 사이드 API 모킹을 위한 MSW(Mock Service Worker) 설정
- './apply'에서 클럽 핸들러를 가져와 적용
- `setupWorker`를 통해 일관된 핸들러 초기화 보장
- 클럽 지원서 조회 및 제출을 위한 Mock API 핸들러 추가
- GET /api/club/apply: 유효하지 않은 클럽 ID에 대한 400 에러 처리
- GET /api/club/:clubId/apply: 클럽 ID 유효성 검사 및 모의 데이터 반환
- POST /api/club/:clubId/apply: 클럽 ID 유효성 검사 및 성공 메시지 반환
- 클럽 지원서 API 테스트 케이스 추가 (MSW)
- 정상 요청: 클럽 지원서 조회 및 제출 테스트
- 필수 질문 항목이 비어있지 않은지 확인하는 테스트
- 에러 케이스: 잘못된 클럽 ID 및 클럽 ID 누락 시 400 에러 테스트
- 지원서 제출 성공 및 에러 케이스 확인
- setupWorker에 전달되는 handlers 배열의 타입을 명시적으로 RequestHandler[]로 지정.
- TypeScript에서 발생한 타입 불일치 오류 해결.
- MSW의 엄격한 타입 검사 규칙에 맞도록 코드 수정.
- 하드코딩된 API URL을 API_BASE 상수로 대체
- URL 관리 중앙화로 유지보수성 향상
- 개발 환경에서만 MSW 워커 로드
- 동적 임포트로 초기 로딩 시간 개선
- 불필요한 프로덕션 번들 크기 감소
oesnuj and others added 14 commits June 5, 2025 15:48
Co-Authored-By: Seongwon Seo <seongwon0903@gmail.com>
Co-Authored-By: Seongwon Seo <seongwon0903@gmail.com>
1. CreateApplicationForm 컴포넌트에 클럽 지원서 저장(생성/수정) 기능 추가
2. 클럽 ID를 기반으로 지원서 데이터를 불러오도록 useAdminClubContext와 useGetApplication 훅 사용
3. mockData 의존성을 제거하고 실제 API 데이터를 활용하도록 수정
4. form_title 등의 필드명을 title로 통일하여 코드 일관성 유지

Co-Authored-By: Seongwon Seo <seongwon0903@gmail.com>
Co-Authored-By: Seongwon Seo <seongwon0903@gmail.com>
1. mockData 의존성을 제거하고 실제 API 데이터를 활용하도록 수정
2. useGetApplication 훅을 활용해 클럽 지원서 데이터를 불러오도록 구현

Co-Authored-By: Seongwon Seo <seongwon0903@gmail.com>
…-api-MOA-38

[feature] 지원서 제작/수정/불러오기 API 연결하기
…age-MOA-36

[feature] 동아리 검색 결과가 없을 시 안내 문구 추가
@oesnuj oesnuj requested a review from seongwon030 June 6, 2025 05:37
@oesnuj oesnuj added 💻 FE Frontend 📈 release 릴리즈 배포 labels Jun 6, 2025
@netlify
Copy link

netlify bot commented Jun 6, 2025

Deploy Preview for moadong ready!

Name Link
🔨 Latest commit 551fe47
🔍 Latest deploy log https://app.netlify.com/projects/moadong/deploys/68428f05cd12f00008e346ed
😎 Deploy Preview https://deploy-preview-508--moadong.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 42
Accessibility: 89
Best Practices: 92
SEO: 92
PWA: -
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jun 6, 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 연동, MSW 기반 목(mock) 서버 및 테스트, 스타일 컴포넌트, 타입 정의, 공통 컴포넌트 등이 추가·수정되었습니다. 또한 ESLint, Jest, TypeScript, SDK 초기화 등 개발 환경 설정도 함께 개선되었습니다.

Changes

파일/경로 그룹 변경 요약
frontend/src/pages/AdminPage/application/, frontend/src/pages/AdminPage/application/components/, frontend/src/pages/AdminPage/application/fields/, frontend/src/pages/AdminPage/application/answer/ 지원서 폼 생성/수정/응답 페이지, 질문 빌더, 답변 컴포넌트, 스타일 컴포넌트 신규 도입
frontend/src/apis/application/ 지원서 생성, 조회, 수정 API 함수 신설
frontend/src/hooks/queries/application/, frontend/src/hooks/useAnswers.ts 지원서 데이터 및 답변 관리용 커스텀 훅 추가
frontend/src/types/application.ts, frontend/src/constants/APPLICATION_FORM.ts, frontend/src/constants/INITIAL_FORM_DATA.ts 어플리케이션 폼 관련 타입, 상수, 초기값 정의
frontend/src/components/common/CustomDropDown/, frontend/src/components/common/CustomTextArea/ 커스텀 드롭다운, 텍스트에어리어 컴포넌트 및 스타일 추가
frontend/src/components/common/InputField/ InputField에 readOnly, 색상 커스텀 prop 추가 및 스타일 개선
frontend/src/index.tsx, frontend/src/utils/initSDK.ts SDK 초기화 로직 분리 및 MSW 개발 환경 세팅 추가
frontend/src/mocks/, frontend/public/mockServiceWorker.js MSW 기반 목 API, 핸들러, 유틸, 테스트, mockData 등 목(mock) 인프라 도입
frontend/package.json, frontend/eslint.config.mjs, frontend/jest.config.js, frontend/tsconfig.json ESLint, Jest, TypeScript, 패키지 의존성 등 개발 환경 설정 개선
frontend/src/pages/MainPage/, frontend/src/pages/ClubDetailPage/, frontend/src/components/, frontend/src/context/ React 17+ 환경에 맞춰 불필요한 React import 제거, 일부 스타일 및 로딩/빈값 처리 개선
기타 스타일 컴포넌트, 공통 컴포넌트, 사이드바 등 지원서 관리 탭 추가 및 경로/라우팅 보완

Changes (Table)

파일/경로 그룹 변경 요약
src/pages/AdminPage/application/, src/pages/AdminPage/application/components/, src/pages/AdminPage/application/fields/, src/pages/AdminPage/application/answer/ 지원서 폼 생성/수정/응답 UI, 질문 빌더, 답변 컴포넌트, 스타일 컴포넌트 신규 도입
src/apis/application/ 지원서 생성, 조회, 수정 API 함수 신설
src/hooks/queries/application/, src/hooks/useAnswers.ts 지원서 데이터 및 답변 관리용 커스텀 훅 추가
src/types/application.ts, src/constants/APPLICATION_FORM.ts, src/constants/INITIAL_FORM_DATA.ts 어플리케이션 폼 관련 타입, 상수, 초기값 정의
src/components/common/CustomDropDown/, src/components/common/CustomTextArea/ 커스텀 드롭다운, 텍스트에어리어 컴포넌트 및 스타일 추가
src/components/common/InputField/ InputField에 readOnly, 색상 커스텀 prop 추가 및 스타일 개선
src/index.tsx, src/utils/initSDK.ts SDK 초기화 로직 분리 및 MSW 개발 환경 세팅 추가
src/mocks/, public/mockServiceWorker.js MSW 기반 목 API, 핸들러, 유틸, 테스트, mockData 등 목(mock) 인프라 도입
package.json, eslint.config.mjs, jest.config.js, tsconfig.json ESLint, Jest, TypeScript, 패키지 의존성 등 개발 환경 설정 개선
src/pages/MainPage/, src/pages/ClubDetailPage/, src/components/, src/context/ React 17+ 환경에 맞춰 불필요한 React import 제거, 일부 스타일 및 로딩/빈값 처리 개선
기타 스타일 컴포넌트, 공통 컴포넌트, 사이드바 등 지원서 관리 탭 추가 및 경로/라우팅 보완

Sequence Diagram(s)

sequenceDiagram
    participant Admin as AdminPage
    participant API as Application API
    participant User as UserPage
    participant MSW as Mock Service Worker

    Admin->>API: getApplication(clubId)
    API-->>Admin: application form data

    Admin->>API: createApplication(formData, clubId)
    API-->>Admin: 생성 결과

    Admin->>API: updateApplication(formData, clubId)
    API-->>Admin: 수정 결과

    User->>API: getApplication(clubId)
    API-->>User: application form data

    User->>User: 입력값 관리 (useAnswers)

    User->>API: (제출) createApplication(answerData, clubId)
    API-->>User: 제출 결과

    Note over API,MSW: 개발 환경에서는 MSW가 API 요청을 가로채어 목(mock) 데이터 반환
Loading

Possibly related PRs

  • #452: 지원서 생성/수정 컴포넌트 및 관련 타입/상수 변경 등 직접적 코드 레벨 연관
  • #499: 지원서 API 함수 및 React 쿼리 훅 추가, CreateApplicationForm 컴포넌트와의 통합 등 직접적 연관
  • #490: AnswerApplicationForm, useAnswers, QuestionAnswerer 컴포넌트 도입 등 직접적 코드 레벨 연관

Suggested labels

📬 API, ✅ Test, ⚙ Setting, 🛠Fix, ✨ Feature

Suggested reviewers

  • seongwon030
    """
✨ Finishing Touches
  • 📝 Generate Docstrings

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.
    • @coderabbitai modularize this function.
  • 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.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

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 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: 27

🧹 Nitpick comments (43)
frontend/src/components/common/LazyImage/LazyImage.tsx (2)

13-24: IntersectionObserver 비지원 환경 폴백 추가 제안
일부 구형 브라우저나 테스트 환경에서 IntersectionObserver가 지원되지 않을 수 있습니다. Observer 초기화 전점에 아래와 같은 폴백 로직을 추가해 주세요:

if (typeof IntersectionObserver === 'undefined') {
  setIsVisible(true);
  return;
}

37-38: 네이티브 lazy 로딩 속성 추가 제안
<img> 태그에 loading="lazy" 속성을 추가하면 브라우저 기본 lazy 로딩을 활용할 수 있어 IntersectionObserver가 동작하지 않을 경우에도 이미지 지연 로딩을 보장합니다.

frontend/src/mocks/utils/validateClubId.ts (1)

1-4: validateClubId 단위 테스트 추가 권장

현재 유효성 검사 함수에 대한 직접 테스트가 없어, 빈 문자열·길이 불일치·비헥사 문자를 포함한 입력 등을 다루는 단위 테스트를 작성하는 것을 권장합니다. 원하시면 테스트 코드 작성 도움을 드릴 수 있습니다.

frontend/src/apis/application/getApplication.ts (2)

13-17: FIXME 주석을 해결하고 에러 처리를 개선하세요.

FIXME 주석에서 언급된 "지원서가 존재하지 않습니다" 에러 케이스가 적절히 처리되지 않고 있습니다. 현재는 모든 에러를 동일하게 처리하고 있어 사용자 경험이 좋지 않을 수 있습니다.

다음과 같은 개선을 제안합니다:

  } catch (error) {
-   // [x] FIXME:
-   // {"statuscode":"800-1","message":"지원서가 존재하지 않습니다.","data":null}
-   console.error('Error fetching club details', error);
-   throw error;
+   console.error('Error fetching application data', error);
+   
+   // 특정 에러 코드에 따른 처리
+   if (error instanceof Error && error.message.includes('800-1')) {
+     throw new Error('지원서가 존재하지 않습니다.');
+   }
+   
+   throw error;
  }

더 구체적인 에러 처리 로직을 구현할 수 있도록 도움을 드릴까요?


15-15: 에러 메시지를 정확하게 수정하세요.

콘솔 에러 메시지가 'club details'라고 되어 있지만, 실제로는 application 데이터를 가져오는 함수입니다.

-   console.error('Error fetching club details', error);
+   console.error('Error fetching application data', error);
frontend/src/pages/AdminPage/application/components/QuestionTitle/QuestionTitle.styles.ts (1)

10-10: 색상값을 상수로 분리하세요.

#ff5414 색상값이 여러 곳에서 반복 사용되고 있습니다. 테마 시스템이나 색상 상수를 활용하여 일관성을 유지하세요.

색상 상수를 정의하거나 기존 테마 시스템을 활용하는 것을 권장합니다:

+const COLORS = {
+  PRIMARY_ORANGE: '#ff5414',
+  // 다른 색상들...
+};

export const QuestionTitleId = styled.p`
- color: #ff5414;
+ color: ${COLORS.PRIMARY_ORANGE};
  font-size: 1.25rem;
  font-weight: 700;
  line-height: normal;
`;

export const QuestionRequired = styled.div`
  width: 8px;
  height: 8px;
  border-radius: 50%;
- background-color: #ff5414;
+ background-color: ${COLORS.PRIMARY_ORANGE};
  margin-left: 14px;
`;

Also applies to: 39-39

frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx (2)

4-4: 사용되지 않는 import 제거 필요

useGetClubDetail 훅이 import되었지만 컴포넌트에서 실제로 사용되지 않고 있습니다.

-import { useGetClubDetail } from '@/hooks/queries/club/useGetClubDetail';

51-52: TODO 주석 해결 필요

지원서 관련 리다이렉트 기능이 TODO로 남아있습니다. 구현 계획이 있다면 이슈로 추적하는 것을 권장합니다.

이 TODO를 이슈로 생성하거나 구현 방안을 제안드릴까요?

frontend/src/hooks/useAnswers.ts (3)

7-19: 성능 최적화 고려사항

현재 구현은 매번 전체 배열을 필터링하고 새 배열을 생성합니다. 답변이 많아질 경우 성능에 영향을 줄 수 있습니다.

+ import { useState, useCallback } from 'react';

- const updateSingleAnswer = (id: number, value: string) => {
+ const updateSingleAnswer = useCallback((id: number, value: string) => {
    setAnswers((prev) => [
      ...prev.filter((a) => a.id !== id),
      { id, answer: value },
    ]);
- };
+ }, []);

- const updateMultiAnswer = (id: number, values: string[]) => {
+ const updateMultiAnswer = useCallback((id: number, values: string[]) => {
    setAnswers((prev) => [
      ...prev.filter((a) => a.id !== id),
      ...values.map((v) => ({ id, answer: v })),
    ]);
- };
+ }, []);

21-27: 함수 최적화 및 타입 안전성 강화

onAnswerChange 함수도 useCallback으로 최적화하고, 빈 배열 처리를 개선할 수 있습니다.

- const onAnswerChange = (id: number, value: string | string[]) => {
+ const onAnswerChange = useCallback((id: number, value: string | string[]) => {
    if (Array.isArray(value)) {
+     if (value.length === 0) {
+       // 빈 배열인 경우 해당 ID의 모든 답변 제거
+       setAnswers((prev) => prev.filter((a) => a.id !== id));
+       return;
+     }
      updateMultiAnswer(id, value);
    } else {
      updateSingleAnswer(id, value);
    }
- };
+ }, [updateSingleAnswer, updateMultiAnswer]);

29-31: API 일관성 향상

getAnswersById 함수도 useCallback으로 최적화하고, 반환값에 대한 명확성을 높일 수 있습니다.

- const getAnswersById = (id: number) =>
-   answers.filter((a) => a.id === id).map((a) => a.answer);
+ const getAnswersById = useCallback((id: number): string[] => 
+   answers.filter((a) => a.id === id).map((a) => a.answer),
+ [answers]);
frontend/src/pages/AdminPage/application/fields/Choice.styles.ts (2)

3-13: UX 개선을 위한 호버 효과 추가 권장

사용자 경험 향상을 위해 호버 효과를 추가하는 것을 권장합니다.

export const AddItemButton = styled.button`
  padding: 8px 12px;
  border-radius: 6px;
  border: 1px solid #ccc;
  font-size: 0.875rem;
  font-weight: 500;
  background: white;
  color: #555;
  margin-top: 8px;
  cursor: pointer;
+ 
+ &:hover {
+   background-color: #f8f9fa;
+   border-color: #999;
+ }
+ 
+ &:focus {
+   outline: 2px solid #007bff;
+   outline-offset: 2px;
+ }
`;

22-30: 색상 값 하드코딩 개선 및 접근성 향상

하드코딩된 색상값 대신 테마 변수 사용을 권장하며, 접근성을 위한 스타일 개선이 필요합니다.

export const DeleteButton = styled.button`
  font-size: 0.75rem;
  padding: 4px 8px;
  border-radius: 4px;
- background-color: #ffecec;
- color: #e33;
- border: 1px solid #f99;
+ background-color: ${({ theme }) => theme.colors.danger.light || '#ffecec'};
+ color: ${({ theme }) => theme.colors.danger.main || '#e33'};
+ border: 1px solid ${({ theme }) => theme.colors.danger.border || '#f99'};
  cursor: pointer;
+ 
+ &:hover {
+   background-color: ${({ theme }) => theme.colors.danger.hover || '#ffe0e0'};
+ }
+ 
+ &:focus {
+   outline: 2px solid ${({ theme }) => theme.colors.danger.main || '#e33'};
+   outline-offset: 2px;
+ }
`;

테마 시스템 도입을 고려해보세요. 색상 일관성과 유지보수성이 크게 향상됩니다.

frontend/src/pages/AdminPage/application/fields/LongText.tsx (2)

32-39: 에러 처리 및 사용성 개선

onAnswerChange가 undefined일 수 있는 상황에 대한 처리가 있지만, 사용자에게 더 명확한 피드백을 제공할 수 있습니다.

<CustomTextArea
  value={answer}
- onChange={(e) => onAnswerChange?.(e.target.value)}
+ onChange={(e) => {
+   if (onAnswerChange) {
+     onAnswerChange(e.target.value);
+   }
+ }}
  placeholder={APPLICATION_FORM.LONG_TEXT.placeholder}
  disabled={mode === 'builder'}
  showMaxChar={mode === 'answer'}
  maxLength={APPLICATION_FORM.LONG_TEXT.maxLength}
+ required={required}
+ aria-label={title ? `${title} 답변 입력` : '장문 답변 입력'}
/>

18-41: 컴포넌트 구조 및 접근성 개선

전체적인 컴포넌트 구조는 좋지만, 접근성과 시맨틱 마크업을 개선할 수 있습니다.

return (
- <div>
+ <fieldset>
+   {title && <legend className="sr-only">{title}</legend>}
    <QuestionTitle
      id={id}
      title={title}
      required={required}
      mode={mode}
      onTitleChange={onTitleChange}
    />
    <QuestionDescription
      description={description}
      mode={mode}
      onDescriptionChange={onDescriptionChange}
    />
    <CustomTextArea
      value={answer}
      onChange={(e) => onAnswerChange?.(e.target.value)}
      placeholder={APPLICATION_FORM.LONG_TEXT.placeholder}
      disabled={mode === 'builder'}
      showMaxChar={mode === 'answer'}
      maxLength={APPLICATION_FORM.LONG_TEXT.maxLength}
+     id={`question-${id}`}
+     aria-describedby={description ? `description-${id}` : undefined}
    />
- </div>
+ </fieldset>
);
frontend/src/apis/application/createApplication.ts (1)

27-31: 에러 핸들링 개선을 고려해보세요.

console.error 사용이 개발 환경에서는 유용하지만, 프로덕션 환경에서는 로깅 시스템 사용을 고려해보세요.

  } catch (error) {
-   console.error('지원서 제출 중 오류 발생:', error);
+   // TODO: 프로덕션 환경에서는 로깅 시스템 사용 고려
+   if (process.env.NODE_ENV === 'development') {
+     console.error('지원서 제출 중 오류 발생:', error);
+   }
    throw error;
  }
frontend/src/pages/AdminPage/application/answer/AnswerApplicationForm.styles.ts (1)

23-43: submitButton 네이밍 일관성을 개선해보세요.

다른 컴포넌트들은 PascalCase를 사용하는데 submitButton만 camelCase를 사용하고 있습니다. 일관성을 위해 SubmitButton으로 변경하는 것을 고려해보세요.

-export const submitButton = styled.button`
+export const SubmitButton = styled.button`

버튼의 스타일링과 인터랙션 효과(hover, active)는 잘 구현되었습니다.

frontend/src/components/common/InputField/InputField.tsx (1)

81-85: 인라인 스타일 사용에 대한 일관성 고려가 필요합니다.

기존 컴포넌트가 styled-components를 사용하고 있는데, 새로운 색상 props는 인라인 스타일로 적용되고 있습니다. 스타일링 접근 방식의 일관성을 위해 styled-components로 통합하는 것을 고려해보세요.

다음과 같이 styled-components에서 props를 받아 처리하는 방식으로 변경할 수 있습니다:

        <Styled.Input
          type={type === 'password' && !isPasswordVisible ? 'password' : 'text'}
          value={value}
          onChange={handleChange}
          placeholder={placeholder}
          maxLength={maxLength}
          disabled={disabled}
          hasError={isError}
          readOnly={readOnly}
-         style={{
-           background: bgColor || '#FFF',
-           color: textColor,
-           borderColor: borderColor,
-         }}
+         bgColor={bgColor}
+         textColor={textColor}
+         borderColor={borderColor}
        />

그리고 InputField.styles.ts에서 해당 props를 처리하도록 수정합니다.

frontend/src/components/common/CustomDropDown/CustomDropDown.styles.ts (1)

24-34: 접근성 개선을 위한 추가 고려사항이 있습니다.

드롭다운 리스트의 스타일링은 잘 되어 있지만, 접근성을 위한 몇 가지 개선사항을 고려해보세요.

다음과 같은 접근성 개선을 권장합니다:

export const OptionList = styled.ul`
  position: absolute;
  top: 100%;
  left: 0;
  width: 100%;
  background: #fff;
  border-radius: 6px;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
  z-index: 10;
  list-style: none;
+ border: 1px solid #e0e0e0;
+ max-height: 200px;
+ overflow-y: auto;
`;

이렇게 하면 시각적 구분이 더 명확해지고, 많은 옵션이 있을 때 스크롤 처리가 가능합니다.

frontend/src/components/common/CustomTextArea/CustomTextArea.tsx (3)

4-4: TODO 주석을 Issue로 추적하는 것을 권장합니다.

InputField 컴포넌트와의 중복 코드 리팩토링에 대한 TODO가 있습니다. 이를 Issue로 등록하여 추후 작업을 체계적으로 관리하는 것이 좋겠습니다.

해당 리팩토링 작업을 위한 Issue를 생성해드릴까요?


44-50: 문자 수 제한 처리 로직을 개선할 수 있습니다.

현재 로직에서는 maxLength를 초과하는 입력을 완전히 차단하지만, 사용자에게 더 나은 피드백을 제공할 수 있습니다.

다음과 같이 개선할 수 있습니다:

  const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
    const inputValue = e.target.value;
    if (maxLength !== undefined && inputValue.length > maxLength) {
-     return;
+     // 최대 길이까지만 잘라서 입력
+     e.target.value = inputValue.slice(0, maxLength);
    }
    onChange?.(e);
  };

65-69: 문자 수 표시 조건을 단순화할 수 있습니다.

showMaxChar와 maxLength가 모두 확인되는 조건문을 더 명확하게 작성할 수 있습니다.

-       {showMaxChar && maxLength !== undefined && (
+       {showMaxChar && maxLength && (
frontend/src/pages/AdminPage/application/components/QuestionAnswerer/QuestionAnswerer.tsx (1)

25-61: switch 문의 케이스 처리를 개선할 수 있습니다.

현재 구조는 명확하지만, 관련 있는 케이스들을 더 체계적으로 그룹화할 수 있습니다.

다음과 같이 개선할 수 있습니다:

  switch (question.type) {
    case 'NAME':
    case 'EMAIL':
    case 'PHONE_NUMBER':
    case 'SHORT_TEXT': {
+     // 단답형 입력 필드들을 그룹화
      return (
        <ShortText
          {...baseProps}
          answer={selectedAnswers[0] ?? ''}
          onAnswerChange={(value) => onChange(question.id, value)}
        />
      );
+   }

    case 'LONG_TEXT': {
      return (
        <LongText
          {...baseProps}
          answer={selectedAnswers[0] ?? ''}
          onAnswerChange={(value) => onChange(question.id, value)}
        />
      );
+   }

    case 'CHOICE':
    case 'MULTI_CHOICE': {
+     // 선택형 입력 필드들을 그룹화  
      return (
        <Choice
          {...baseProps}
          items={question.items}
          isMulti={question.type === 'MULTI_CHOICE'}
          answer={selectedAnswers}
          onAnswerChange={(value) => onChange(question.id, value)}
        />
      );
+   }
frontend/eslint.config.mjs (1)

1-55: ESLint 설정이 체계적으로 개선되었습니다

모듈형 접근에서 명시적 설정으로의 전환이 잘 이루어졌으며, TypeScript와 React에 대한 적절한 설정이 포함되어 있습니다. 다음과 같은 추가 개선을 고려해보세요:

  1. 접근성 규칙 추가: eslint-plugin-jsx-a11y 추가 고려
  2. 더 엄격한 TypeScript 규칙: @typescript-eslint/no-explicit-any, @typescript-eslint/prefer-const
frontend/src/pages/AdminPage/application/components/QuestionDescription/QuestionDescription.tsx (2)

60-65: 중복된 길이 검증 제거를 고려하세요

maxLength HTML 속성이 이미 설정되어 있어 추가 검증이 불필요할 수 있습니다.

        onChange={(e) => {
          const value = e.target.value;
-          if (value.length <= APPLICATION_FORM.QUESTION_DESCRIPTION.maxLength) {
            onDescriptionChange?.(value);
-          }
        }}

51-68: 불필요한 Fragment 제거하세요

단일 요소를 렌더링할 때는 Fragment가 불필요합니다.

  return (
-    <>
      <QuestionDescriptionText
        ref={textAreaRef}
        value={description}
        maxLength={APPLICATION_FORM.QUESTION_DESCRIPTION.maxLength}
        placeholder={APPLICATION_FORM.QUESTION_DESCRIPTION.placeholder}
        aria-label='질문 설명'
        readOnly={mode === 'answer'}
        onChange={(e) => {
          const value = e.target.value;
          if (value.length <= APPLICATION_FORM.QUESTION_DESCRIPTION.maxLength) {
            onDescriptionChange?.(value);
          }
        }}
      />
-    </>
  );
frontend/src/pages/AdminPage/application/components/QuestionTitle/QuestionTitle.tsx (1)

29-34: 중복된 길이 검증 제거를 고려하세요

QuestionDescription과 마찬가지로 maxLength HTML 속성이 이미 설정되어 있어 추가 검증이 불필요할 수 있습니다.

        onChange={(e) => {
          const value = e.target.value;
-          if (value.length <= APPLICATION_FORM.QUESTION_TITLE.maxLength) {
            onTitleChange?.(value);
-          }
        }}
frontend/src/mocks/api/applyHandlers.test.ts (2)

40-44: 타입 단언보다 타입 가드 사용을 고려하세요

현재 as 키워드를 사용한 타입 단언이 많이 사용되고 있습니다. 더 안전한 타입 체크를 위해 타입 가드를 고려해보세요.

// 타입 가드 함수 추가
function isClubApplyResponse(data: any): data is ClubApplyResponse {
  return data && typeof data.clubId === 'string' && typeof data.form_title === 'string' && Array.isArray(data.questions);
}

// 사용 예시
it('클럽 지원서를 정상적으로 불러온다.', () => {
  expect(response.status).toBe(200);
  if (isClubApplyResponse(data)) {
    expect(data.form_title).toBeDefined();
    expect(data.questions.length).toBeGreaterThan(0);
  }
});

86-97: 테스트 설명과 실제 테스트 내용이 일치하는지 확인하세요

"객관식 질문 답변 제출 성공" 테스트에서 실제로는 특정 질문 ID(1, 99)에 대한 답변을 테스트하고 있습니다. 더 명확한 테스트 설명이나 실제 객관식 질문에 대한 테스트로 수정이 필요할 수 있습니다.

frontend/src/mocks/data/mockData.ts (1)

84-89: 과도하게 긴 목 데이터 텍스트

목 데이터의 샘플 텍스트가 불필요하게 길어 가독성을 해칩니다. 간결한 텍스트로 변경하는 것이 좋습니다.

       items: [
         {
-          value:
-            '주관식 서술형은 500자 이하여야 합니다.주관식 서술형은 500자 이하여야 합니다.주관식 서술형은 500자 이하여야 합니다.주관식 서술형은 500자 이하여야 합니다.주관식 서술형은 500자 이하여야 합니다.주관식 서술형은 500자 이하여야 합니다.주관식 서술형은 500자 이하여야 합니다.주관식 서술형은 500자 이하여야 합니다.주관식 서술형은 500자 이하여야 합니다.주관식 서술형은 500자 이하여야 합니다.주관식 서술형은 500자 이하여야 합니다.주관식 서술형은 500자 이하여야 합니다.주관식 서술형은 500자 이하여야 합니다.주관식 서술형은 500자 이하여야 합니다.주관식 서술형은 500자 이하여야 합니다.주관식 서술형은 500자 이하여야 합니다.주관식 서술형은 500자 이하여야 합니다.주관식 서술형은 500자 이하여야 합니다.주관식 서술형은 500자 이하여야 합니다.주관식 서술형은 500자 이하여야 합니다.주관식 서술형은 500자 이하여야 합니다.주관식 서술형은 500자 이하여야 합니다.주관식 서술형은 500자 이하여야 합니다.주관식 서술형은 500자 이하여야 합니다.주관식 서술형은 500자 이하여야 합니다.주관식 서술형은 500자 이하여야 합니다.주관식 서술형은 500자 이하여야 합니다.주관식 서술형은 500자 이하여야 합니다.주관식 서술형은 500자 이하여야 합니다.주관식 서술형은 500자 이하여야 합니다.',
+          value: '동아리 활동을 통해 얻고자 하는 목표와 기대하는 바를 자유롭게 서술해주세요. (500자 이하)',
         },
       ],
frontend/src/pages/AdminPage/application/fields/Choice.tsx (3)

25-31: 불필요한 한글 주석 제거

코드 자체가 명확하므로 중복되는 한글 주석을 제거하는 것이 좋습니다.

-  // — 아이템 텍스트 변경(빌더 모드 전용)
   const handleItemChange = (index: number, newValue: string) => {
     const updated = items.map((item, i) =>
       i === index ? { ...item, value: newValue } : item,
     );
     onItemsChange?.(updated);
   };

33-37: 빈 아이템 추가 시 기본값 설정 고려

새 아이템 추가 시 사용자 친화적인 기본값을 제공하면 더 나은 UX를 제공할 수 있습니다.

-  // — 아이템 추가(빌더 모드 전용)
   const handleAddItem = () => {
     if (items.length >= MAX_ITEMS) return;
-    onItemsChange?.([...items, { value: '' }]);
+    onItemsChange?.([...items, { value: `선택 ${items.length + 1}` }]);
   };

39-44: 중복되거나 불필요한 주석 정리

코드의 가독성을 위해 불필요한 주석들을 제거하는 것이 좋습니다.

주석 제거 필요한 부분:

  • 39번 줄: // — 아이템 삭제(빌더 모드 전용)
  • 59번 줄: // 다중 선택: 이미 포함되어 있으면 제거, 아니면 추가
  • 82번 줄: // ▶ selected 대신, answer.includes(item.value)로 판별

이러한 주석들은 코드 자체로 충분히 명확하므로 제거해도 무방합니다.

Also applies to: 59-59, 82-83

frontend/src/components/common/InputField/InputField.styles.ts (1)

3-3: readOnly prop 타입 정의를 명확하게 하세요.

InputContainer에 readOnly prop이 추가되었지만 실제로 사용되지 않고 있습니다. 사용하지 않는다면 제거하는 것이 좋겠습니다.

-export const InputContainer = styled.div<{ width: string; readOnly?: boolean }>`
+export const InputContainer = styled.div<{ width: string }>`
frontend/src/pages/AdminPage/application/CreateApplicationForm.styles.ts (1)

50-70: submitButton 네이밍 규칙을 통일하세요.

다른 styled-components는 PascalCase를 사용하는데 submitButton만 camelCase를 사용하고 있습니다.

-export const submitButton = styled.button`
+export const SubmitButton = styled.button`
frontend/src/pages/AdminPage/application/components/QuestionBuilder/QuestionBuilder.tsx (2)

30-40: useEffect 의존성을 최적화하세요.

useEffect에서 type만 의존하고 있는데, selectionType 설정 로직을 더 간단하게 할 수 있습니다.

-  const [selectionType, setSelectionType] = useState<'single' | 'multi'>(
-    type === 'MULTI_CHOICE' ? 'multi' : 'single',
-  );
-
-  useEffect(() => {
-    if (type === 'MULTI_CHOICE') {
-      setSelectionType('multi');
-    } else if (type === 'CHOICE') {
-      setSelectionType('single');
-    }
-  }, [type]);
+  const selectionType = type === 'MULTI_CHOICE' ? 'multi' : 'single';

useState와 useEffect 대신 computed value로 처리하면 더 간단해집니다.


92-117: 선택 타입 토글 기능을 개선하세요.

selectionType 상태가 불필요하다면, 토글 버튼의 active 상태도 type prop으로 직접 결정할 수 있습니다.

  const renderSelectionToggle = () => {
    if (type !== 'CHOICE' && type !== 'MULTI_CHOICE') return null;

    return (
      <Styled.SelectionToggleWrapper>
        <Styled.SelectionToggleButton
-          active={selectionType === 'single'}
+          active={type === 'CHOICE'}
          onClick={() => {
-            setSelectionType('single');
            onTypeChange?.('CHOICE');
          }}
        >
          단일선택
        </Styled.SelectionToggleButton>
        <Styled.SelectionToggleButton
-          active={selectionType === 'multi'}
+          active={type === 'MULTI_CHOICE'}
          onClick={() => {
-            setSelectionType('multi');
            onTypeChange?.('MULTI_CHOICE');
          }}
        >
          다중선택
        </Styled.SelectionToggleButton>
      </Styled.SelectionToggleWrapper>
    );
  };
frontend/src/pages/AdminPage/application/components/QuestionBuilder/QuestionBuilder.styles.ts (4)

3-9: 테마 기반 간격 사용 고려
gap: 4px, max-width: 140px 같은 고정값 대신 스타일드 컴포넌트의 theme.spacing 또는 디자인 토큰을 활용하면 전체 레이아웃 일관성과 유지보수성이 올라갑니다.


31-53: 명암 대비 및 전환 속도 검토

  • #ff5000 점(원)의 대비(약 3.28:1)가 UI 구성요소 기준 최소 3:1을 겨우 맞추므로, 접근성 가이드라인을 준수하는지 확인하세요.
  • background-color 0.2s, opacity 0.1s 전환 속도가 UI 일관성 관점에서 적절한지 검토를 권장합니다.

62-76: 포커스·호버 상태 추가 제안
현재 SelectionToggleButton에 포커스나 호버 상태 정의가 빠져 있어 키보드 네비게이션/마우스 오버 시 사용자 피드백이 없습니다.

&:hover { background-color: #ccc; }
&:focus-visible { outline: 2px solid #ff5000; }

78-82: 테마 기반 간격 사용 고려
gap: 36px 역시 디자인 토큰으로 대체하면 레이아웃 일관성 관리에 도움이 됩니다.

frontend/src/types/application.ts (2)

5-14: items 필드 옵셔널 처리 제안
모든 질문에 items: { value: string }[]가 필수로 지정돼 있어, 단답형/장문형 질문에도 빈 배열이 전달됩니다.

  • items?: { value: string }[]; 옵션 처리
  • 또는 ChoiceQuestion 전용 인터페이스로 분리

25-33: 인터페이스 명칭 가독성 향상 제안
QuestionComponentProps는 공통 속성을 담고 있으므로, BaseQuestionProps 등으로 명칭을 바꾸면 역할이 더 명확해집니다.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 015ffd8 and 8ecc61a.

⛔ Files ignored due to path filters (2)
  • frontend/package-lock.json is excluded by !**/package-lock.json
  • frontend/src/assets/images/icons/drop_button_icon.svg is excluded by !**/*.svg
📒 Files selected for processing (83)
  • frontend/eslint.config.mjs (1 hunks)
  • frontend/jest.config.js (1 hunks)
  • frontend/package.json (3 hunks)
  • frontend/public/mockServiceWorker.js (1 hunks)
  • frontend/src/App.tsx (3 hunks)
  • frontend/src/apis/application/createApplication.ts (1 hunks)
  • frontend/src/apis/application/getApplication.ts (1 hunks)
  • frontend/src/apis/application/updateApplication.ts (1 hunks)
  • frontend/src/components/ClubLogo/ClubLogo.tsx (0 hunks)
  • frontend/src/components/ClubStateBox/ClubStateBox.tsx (0 hunks)
  • frontend/src/components/ClubTag/ClubTag.tsx (0 hunks)
  • frontend/src/components/common/Button/Button.tsx (0 hunks)
  • frontend/src/components/common/CustomDropDown/CustomDropDown.styles.ts (1 hunks)
  • frontend/src/components/common/CustomDropDown/CustomDropDown.tsx (1 hunks)
  • frontend/src/components/common/CustomTextArea/CustomTextArea.styles.ts (1 hunks)
  • frontend/src/components/common/CustomTextArea/CustomTextArea.tsx (1 hunks)
  • frontend/src/components/common/Footer/Footer.tsx (0 hunks)
  • frontend/src/components/common/Header/Header.tsx (0 hunks)
  • frontend/src/components/common/InputField/InputField.styles.ts (4 hunks)
  • frontend/src/components/common/InputField/InputField.tsx (4 hunks)
  • frontend/src/components/common/LazyImage/LazyImage.test.tsx (0 hunks)
  • frontend/src/components/common/LazyImage/LazyImage.tsx (1 hunks)
  • frontend/src/components/common/SearchBox/SearchBox.tsx (1 hunks)
  • frontend/src/constants/APPLICATION_FORM.ts (1 hunks)
  • frontend/src/constants/INITIAL_FORM_DATA.ts (1 hunks)
  • frontend/src/context/AdminClubContext.tsx (1 hunks)
  • frontend/src/context/SearchContext.tsx (1 hunks)
  • frontend/src/hooks/queries/application/useGetApplication.ts (1 hunks)
  • frontend/src/hooks/queries/club/useGetCardList.ts (1 hunks)
  • frontend/src/hooks/queries/club/useGetClubDetail.ts (1 hunks)
  • frontend/src/hooks/useAnswers.ts (1 hunks)
  • frontend/src/index.tsx (1 hunks)
  • frontend/src/mocks/api/apply.ts (1 hunks)
  • frontend/src/mocks/api/applyHandlers.test.ts (1 hunks)
  • frontend/src/mocks/api/index.ts (1 hunks)
  • frontend/src/mocks/api/utils/request.ts (1 hunks)
  • frontend/src/mocks/constants/clubApi.ts (1 hunks)
  • frontend/src/mocks/constants/error.ts (1 hunks)
  • frontend/src/mocks/data/mockData.ts (1 hunks)
  • frontend/src/mocks/mswDevSetup.ts (1 hunks)
  • frontend/src/mocks/utils/createApiUrl.test.ts (1 hunks)
  • frontend/src/mocks/utils/createApiUrl.ts (1 hunks)
  • frontend/src/mocks/utils/validateClubId.ts (1 hunks)
  • frontend/src/pages/AdminPage/AdminPage.tsx (0 hunks)
  • frontend/src/pages/AdminPage/application/CreateApplicationForm.styles.ts (1 hunks)
  • frontend/src/pages/AdminPage/application/CreateApplicationForm.tsx (1 hunks)
  • frontend/src/pages/AdminPage/application/answer/AnswerApplicationForm.styles.ts (1 hunks)
  • frontend/src/pages/AdminPage/application/answer/AnswerApplicationForm.tsx (1 hunks)
  • frontend/src/pages/AdminPage/application/components/QuestionAnswerer/QuestionAnswerer.tsx (1 hunks)
  • frontend/src/pages/AdminPage/application/components/QuestionBuilder/QuestionBuilder.styles.ts (1 hunks)
  • frontend/src/pages/AdminPage/application/components/QuestionBuilder/QuestionBuilder.tsx (1 hunks)
  • frontend/src/pages/AdminPage/application/components/QuestionDescription/QuestionDescription.tsx (1 hunks)
  • frontend/src/pages/AdminPage/application/components/QuestionTitle/QuestionTitle.styles.ts (1 hunks)
  • frontend/src/pages/AdminPage/application/components/QuestionTitle/QuestionTitle.tsx (1 hunks)
  • frontend/src/pages/AdminPage/application/fields/Choice.styles.ts (1 hunks)
  • frontend/src/pages/AdminPage/application/fields/Choice.tsx (1 hunks)
  • frontend/src/pages/AdminPage/application/fields/LongText.tsx (1 hunks)
  • frontend/src/pages/AdminPage/application/fields/ShortText.tsx (1 hunks)
  • frontend/src/pages/AdminPage/auth/LoginTab/LoginTab.tsx (1 hunks)
  • frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx (2 hunks)
  • frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (1 hunks)
  • frontend/src/pages/ClubDetailPage/components/BackNavigationBar/BackNavigationBar.tsx (0 hunks)
  • frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx (2 hunks)
  • frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx (0 hunks)
  • frontend/src/pages/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.tsx (0 hunks)
  • frontend/src/pages/ClubDetailPage/components/ClubProfile/ClubProfile.tsx (0 hunks)
  • frontend/src/pages/ClubDetailPage/components/DeadlineBadge/DeadlineBadge.tsx (0 hunks)
  • frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.styles.ts (1 hunks)
  • frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsx (0 hunks)
  • frontend/src/pages/ClubDetailPage/components/InfoTabs/InfoTabs.tsx (1 hunks)
  • frontend/src/pages/ClubDetailPage/components/IntroduceBox/IntroduceBox.styles.ts (1 hunks)
  • frontend/src/pages/ClubDetailPage/components/IntroduceBox/IntroduceBox.tsx (0 hunks)
  • frontend/src/pages/ClubDetailPage/components/PhotoList/PhotoList.tsx (1 hunks)
  • frontend/src/pages/MainPage/MainPage.styles.ts (1 hunks)
  • frontend/src/pages/MainPage/MainPage.tsx (4 hunks)
  • frontend/src/pages/MainPage/components/Banner/Banner.tsx (1 hunks)
  • frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx (0 hunks)
  • frontend/src/pages/MainPage/components/ClubCard/ClubCard.tsx (1 hunks)
  • frontend/src/pages/MainPage/components/StatusRadioButton/StatusRadioButton.tsx (1 hunks)
  • frontend/src/styles/Global.styles.ts (1 hunks)
  • frontend/src/types/application.ts (1 hunks)
  • frontend/src/utils/initSDK.ts (1 hunks)
  • frontend/tsconfig.json (1 hunks)
💤 Files with no reviewable changes (16)
  • frontend/src/pages/ClubDetailPage/components/IntroduceBox/IntroduceBox.tsx
  • frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsx
  • frontend/src/components/common/LazyImage/LazyImage.test.tsx
  • frontend/src/components/ClubStateBox/ClubStateBox.tsx
  • frontend/src/pages/ClubDetailPage/components/ClubProfile/ClubProfile.tsx
  • frontend/src/pages/AdminPage/AdminPage.tsx
  • frontend/src/pages/ClubDetailPage/components/DeadlineBadge/DeadlineBadge.tsx
  • frontend/src/components/common/Button/Button.tsx
  • frontend/src/components/ClubTag/ClubTag.tsx
  • frontend/src/components/ClubLogo/ClubLogo.tsx
  • frontend/src/pages/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.tsx
  • frontend/src/pages/ClubDetailPage/components/BackNavigationBar/BackNavigationBar.tsx
  • frontend/src/components/common/Footer/Footer.tsx
  • frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx
  • frontend/src/components/common/Header/Header.tsx
  • frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx
🧰 Additional context used
🧠 Learnings (1)
frontend/src/mocks/constants/clubApi.ts (1)
Learnt from: seongwon030
PR: Moadong/moadong#418
File: frontend/src/mocks/api/clubHandlers.test.ts:0-0
Timestamp: 2025-05-16T06:25:11.193Z
Learning: API 관련 상수(예: API_BASE)는 frontend/src/mocks/constants/api.ts에 정의되어 있으며, 다른 파일에서 재정의하지 말고 이 파일에서 import하여 사용해야 합니다.
🧬 Code Graph Analysis (16)
frontend/src/mocks/api/index.ts (1)
frontend/src/mocks/api/apply.ts (1)
  • applyHandlers (7-72)
frontend/src/constants/INITIAL_FORM_DATA.ts (1)
frontend/src/types/application.ts (1)
  • ApplicationFormData (48-51)
frontend/src/mocks/utils/createApiUrl.ts (2)
frontend/src/mocks/utils/validateClubId.ts (1)
  • validateClubId (1-4)
frontend/src/mocks/constants/clubApi.ts (1)
  • API_BASE (1-1)
frontend/src/mocks/mswDevSetup.ts (1)
frontend/src/mocks/api/index.ts (1)
  • handlers (3-3)
frontend/src/hooks/useAnswers.ts (1)
frontend/src/types/application.ts (1)
  • AnswerItem (53-56)
frontend/src/pages/AdminPage/application/fields/LongText.tsx (2)
frontend/src/types/application.ts (1)
  • TextProps (35-38)
frontend/src/constants/APPLICATION_FORM.ts (1)
  • APPLICATION_FORM (1-22)
frontend/src/index.tsx (2)
frontend/src/utils/initSDK.ts (3)
  • initializeMixpanel (5-16)
  • initializeChannelService (18-25)
  • initializeSentry (27-38)
frontend/src/mocks/mswDevSetup.ts (1)
  • worker (5-5)
frontend/src/mocks/utils/createApiUrl.test.ts (2)
frontend/src/mocks/utils/createApiUrl.ts (1)
  • createApiUrl (4-10)
frontend/src/mocks/constants/clubApi.ts (2)
  • CLUB_ID (3-3)
  • API_BASE (1-1)
frontend/src/hooks/queries/club/useGetClubDetail.ts (1)
frontend/src/types/club.ts (1)
  • ClubDetail (16-26)
frontend/src/hooks/queries/club/useGetCardList.ts (1)
frontend/src/types/club.ts (1)
  • Club (3-12)
frontend/src/pages/AdminPage/application/fields/ShortText.tsx (2)
frontend/src/types/application.ts (1)
  • TextProps (35-38)
frontend/src/constants/APPLICATION_FORM.ts (1)
  • APPLICATION_FORM (1-22)
frontend/src/mocks/api/apply.ts (4)
frontend/src/mocks/constants/clubApi.ts (1)
  • API_BASE (1-1)
frontend/src/mocks/constants/error.ts (1)
  • ERROR_MESSAGE (1-5)
frontend/src/mocks/utils/validateClubId.ts (1)
  • validateClubId (1-4)
frontend/src/mocks/data/mockData.ts (1)
  • mockData (25-122)
frontend/src/mocks/data/mockData.ts (1)
frontend/src/types/application.ts (2)
  • QuestionType (3-3)
  • ApplicationFormData (48-51)
frontend/src/pages/AdminPage/application/components/QuestionTitle/QuestionTitle.tsx (1)
frontend/src/constants/APPLICATION_FORM.ts (1)
  • APPLICATION_FORM (1-22)
frontend/src/pages/AdminPage/application/components/QuestionDescription/QuestionDescription.tsx (1)
frontend/src/constants/APPLICATION_FORM.ts (1)
  • APPLICATION_FORM (1-22)
frontend/src/types/application.ts (1)
frontend/src/constants/APPLICATION_FORM.ts (1)
  • QUESTION_LABEL_MAP (24-32)
🪛 Biome (1.9.4)
frontend/public/mockServiceWorker.js

[error] 276-276: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (75)
frontend/src/components/common/SearchBox/SearchBox.tsx (1)

1-1: React default import 제거가 적절합니다.
React 17 이상의 자동 JSX 변환(automatic JSX runtime)을 사용 중이라면 default React import 없이도 정상 작동합니다.

frontend/src/pages/MainPage/components/StatusRadioButton/StatusRadioButton.tsx (1)

1-1:

Details

✅ Verification successful

React import 생략 적절성 확인
import React를 제거하고 useState만 임포트한 것은 React 17 이상의 자동 JSX 변환(Automatic JSX Runtime)을 전제로 합니다. 해당 설정이 tsconfig 및 빌드 환경에 올바르게 반영되어 있는지 검증해 주세요.

아래 스크립트로 tsconfig.json"jsx": "react-jsx" 옵션이 활성화되어 있는지 확인할 수 있습니다:


🏁 Script executed:

#!/bin/bash
# tsconfig.json에서 JSX 설정 확인
grep -R '"jsx"\s*:\s*"react-jsx"' tsconfig.json

Length of output: 96


🏁 Script executed:

#!/bin/bash
# tsconfig.json 파일 위치 및 JSX 설정 확인
echo "=== tsconfig*.json 파일 검색 ==="
find . -type f -name "tsconfig*.json"
echo "=== JSX 설정 확인 ==="
grep -R '"jsx"\s*:' --include 'tsconfig*.json' .

Length of output: 276


React import 생략 확인 완료
frontend/tsconfig.json"jsx": "react-jsx" 설정이 활성화되어 있어 React 17+ 자동 JSX 변환을 정상적으로 사용할 수 있습니다. React import 없이 useState만 임포트한 현재 방식이 적절합니다.

frontend/src/pages/ClubDetailPage/components/IntroduceBox/IntroduceBox.styles.ts (1)

21-21: 모바일 뷰에 폰트 크기 설정 추가
max-width:500px 미디어 쿼리 내에서 font-size를 14px로 지정해 텍스트 가독성을 강화한 점이 적절합니다. 다른 텍스트 컴포넌트(예: IntroduceTitle, Paragraph)에도 동일한 브레이크포인트가 일관되게 적용되는지 확인해주세요.

frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.styles.ts (2)

54-54: DescriptionWrapper 기본 폰트 크기 지정
데스크톱 뷰에서 font-size를 16px로 설정하여 가독성을 높인 변경입니다. 프로젝트 스타일 가이드나 디자인 시스템의 폰트 스케일과 일치하는지 검토해주세요.


56-56: 모바일 뷰에서 폰트 크기 재정의
max-width:500px 이하 화면에 font-size를 14px로 재설정해 반응형 일관성을 유지한 점이 좋습니다. 다른 컴포넌트에도 동일한 미디어쿼리 값이 적용되는지 확인 바랍니다.

frontend/src/context/SearchContext.tsx (1)

1-1:

Details

✅ Verification successful

React 기본 import 제거: 적절함
React 17+의 자동 JSX 변환 기능(react-jsx)을 활용하여 불필요한 기본 import React를 제거하고 Named import만 사용한 것은 코드 경량화 및 가독성 측면에서 적절합니다.


🏁 Script executed:

#!/bin/bash
# tsconfig.json에서 JSX 자동 변환 설정이 react-jsx로 되어 있는지 확인
rg '"jsx"\s*:\s*"react-jsx"' tsconfig.json

Length of output: 98


프로젝트 루트에 tsconfig.json이 없어 설정을 확인할 수 없습니다. 먼저 전체 코드베이스에서 tsconfig*.json 파일을 검색한 뒤 해당 파일들에서 "jsx": "react-jsx" 설정이 있는지 확인하겠습니다.


🏁 Script executed:

#!/bin/bash
# 1. tsconfig*.json 파일 검색
echo "tsconfig 파일 목록:"
find . -type f -name "tsconfig*.json"

# 2. 각 tsconfig 파일에서 JSX 자동 변환 설정 확인
for file in $(find . -type f -name "tsconfig*.json"); do
  echo "----- 확인: $file -----"
  rg '"jsx"\s*:\s*"react-jsx"' "$file" || echo "→ react-jsx 설정 없음"
done

Length of output: 372


React 기본 import 제거: 적절함

  • frontend/tsconfig.json에서 "jsx": "react-jsx" 설정이 확인되어
  • React 17+ 자동 JSX 변환을 활용한 import React 제거가 안전합니다.
frontend/src/components/common/LazyImage/LazyImage.tsx (1)

1-1:

Details

✅ Verification successful

React default import 제거 검증 필요
default React import를 제거하고 named import만 사용하기 위해서는 React 17+의 새로운 JSX 변환(react-jsx) 설정이 필요합니다. tsconfig.json"jsx": "react-jsx"가 적용되어 있는지, React 버전이 17 이상인지 확인하세요.


🏁 Script executed:

#!/bin/bash
# tsconfig.json에서 JSX 변환 설정 확인
rg '"jsx"' tsconfig.json

# 설치된 React 버전 확인
npm list react | head -n 1

Length of output: 128


추가 검증을 위해 프로젝트 내 tsconfig 파일 위치와 JSX 변환 설정, 그리고 패키지에서 설치된 React 버전을 확인하는 스크립트를 실행해 주세요.


🏁 Script executed:

#!/bin/bash
# 1. tsconfig 파일 목록 조회
echo "📂 tsconfig 파일 검색:"
fd tsconfig*.json || find . -maxdepth 2 -name "tsconfig*.json"

# 2. 각 tsconfig 파일에서 JSX 관련 설정 확인
echo ""
echo "⚙️ JSX 설정 확인:"
for cfg in $(fd tsconfig*.json 2>/dev/null); do
  echo "---- $cfg ----"
  rg '"jsx"' "$cfg" || echo "  • jsx 설정 없음"
  rg '"jsxImportSource"' "$cfg" || echo "  • jsxImportSource 설정 없음"
  rg '"react-jsx"' "$cfg" || echo "  • react-jsx 변환 없음"
done

# 3. package.json에서 React 버전 확인
echo ""
echo "🔍 React 버전 확인 (package.json):"
jq -r '.dependencies.react // .devDependencies.react // "react 미설치"' package.json

Length of output: 756


추가 검증을 위해 frontend 폴더 아래 package.json에서 React 버전을 확인하는 스크립트를 실행해 주세요.


🏁 Script executed:

#!/bin/bash
# 1. package.json 파일 목록 조회
echo "📂 package.json 파일 검색:"
fd package.json

# 2. frontend/package.json에서 React 버전 확인
echo ""
echo "🔍 frontend/package.json - React 버전:"
if [ -f frontend/package.json ]; then
  jq -r '.dependencies.react // .devDependencies.react // "react 미설치"' frontend/package.json
else
  echo "frontend/package.json 파일을 찾을 수 없습니다."
fi

Length of output: 324


검증 완료: React 기본 import 제거 안전

  • frontend/tsconfig.json"jsx": "react-jsx" 설정이 적용되어 있습니다.
  • frontend/package.json에서 React 버전 ^19.0.0(≥17)이 확인되었습니다.

따라서 React 17+의 새로운 JSX 변환(react-jsx)이 활성화되어 있어 default import 없이 named import만 사용해도 문제 없습니다.

frontend/src/pages/ClubDetailPage/components/InfoTabs/InfoTabs.tsx (1)

1-1: React 자동 JSX 런타임 적용 확인

명시적 import React from 'react' 제거에 따라, 프로젝트의 TS/Babel 설정에서 React 17+ 자동 JSX 런타임(react-jsx 또는 @babel/preset-reactruntime: "automatic")이 활성화되어 있는지 확인하세요.

frontend/src/styles/Global.styles.ts (1)

9-11: 전역 textarea 폰트 패밀리 설정 완료
textarea 요소에 Pretendard 폰트를 적용해 CustomTextArea를 포함한 모든 textarea의 타이포그래피 일관성을 확보했습니다.

frontend/src/pages/AdminPage/auth/LoginTab/LoginTab.tsx (1)

1-1: React 자동 JSX 런타임 대응 - 기본 React import 제거
JSX 프래그먼트 사용을 위해 React 기본 import가 필요 없어 제거하였으며, TSX react-jsx 설정과 일치합니다.

frontend/src/pages/MainPage/components/Banner/Banner.tsx (1)

1-1: React 자동 JSX 런타임 대응 - 기본 React import 제거
JSX 자동 런타임 설정(react-jsx)에 맞춰 사용되지 않는 기본 React import를 제거해 코드 경량화를 수행했습니다.

frontend/tsconfig.json (1)

20-20: eslint.config.mjs 포함으로 타입 검사 범위 확장
TS 컴파일러 include에 ESLint 설정 파일을 추가해 설정 검사를 적용했습니다. ESLint 및 TS 설정 일관성을 확보합니다.

frontend/src/mocks/api/index.ts (1)

1-3: Mock API 핸들러 중앙집중식 내보내기 구현
applyHandlers를 스프레드하여 handlers 배열로 재구성, MSW 설정에 바로 활용할 수 있게 했습니다. 의도에 부합합니다.

frontend/jest.config.js (1)

5-5:

Details

✅ Verification successful

jest-fixed-jsdom 패키지 의존성 확인 필요

Jest 테스트 환경이 jest-fixed-jsdom으로 변경되었습니다. 이 변경이 올바르게 작동하려면 해당 패키지가 package.json에 정확히 추가되었는지 확인이 필요합니다.

다음 스크립트로 의존성 확인:


🏁 Script executed:

#!/bin/bash
# Description: jest-fixed-jsdom 패키지가 package.json에 올바르게 추가되었는지 확인

# package.json에서 jest-fixed-jsdom 의존성 확인
cat frontend/package.json | jq '.dependencies["jest-fixed-jsdom"] // .devDependencies["jest-fixed-jsdom"]'

Length of output: 117


jest-fixed-jsdom 의존성 확인 완료

  • frontend/jest.config.jstestEnvironment'jest-fixed-jsdom'으로 설정되었습니다.
  • frontend/package.jsondevDependencies"jest-fixed-jsdom": "^0.0.9"가 추가된 것을 확인했습니다.

해당 변경은 올바르게 구성되어 있으므로 추가 조치가 필요 없습니다.

frontend/src/pages/MainPage/MainPage.styles.ts (1)

52-63: 빈 결과 표시 컴포넌트 구현이 적절합니다

새로 추가된 EmptyResult 컴포넌트가 잘 구현되었습니다. 반응형 디자인과 적절한 스타일링이 적용되어 있으며, white-space: pre-line 속성으로 여러 줄 메시지 표시도 지원합니다.

frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx (2)

17-17: 지원서 관리 탭 추가가 적절합니다

새로운 '지원서 관리' 탭이 기존 패턴과 일관되게 추가되었습니다. App.tsx의 새로운 라우트와도 올바르게 연결되어 있습니다.


65-66: 코드 포맷팅 개선

JSX 속성이 더 읽기 쉽게 개선되었습니다.

frontend/src/App.tsx (3)

1-1: React import 최적화

React 18+에서는 JSX Transform으로 인해 React default import가 불필요하므로 제거가 적절합니다.


17-18: 새로운 application 컴포넌트 import

지원서 생성 및 답변 기능을 위한 컴포넌트들이 올바르게 import되었습니다.


68-71: 관리자 지원서 편집 라우트 추가

/admin/application-edit 라우트가 PrivateRoute로 적절히 보호되어 추가되었습니다. 사이드바의 새로운 탭과도 일치합니다.

frontend/src/constants/INITIAL_FORM_DATA.ts (1)

3-23: 초기 폼 데이터 구조가 잘 설계되었습니다.

초기 폼 데이터가 적절하게 구성되어 있으며, 기본적으로 필요한 질문 유형들(단답형, 선택형)을 포함하고 있습니다. 모든 질문이 기본적으로 필수로 설정된 것도 합리적입니다.

frontend/src/mocks/utils/createApiUrl.ts (1)

4-10: API URL 생성 함수가 올바르게 구현되었습니다.

입력 검증, 에러 처리, URL 구성이 모두 적절하게 처리되어 있습니다. 기본 액션 매개변수('apply')도 합리적인 선택입니다.

frontend/src/mocks/constants/clubApi.ts (2)

3-3: CLUB_ID 상수가 적절하게 정의되었습니다.

MongoDB ObjectId 형식의 유효한 클럽 ID가 테스트 목적으로 잘 정의되어 있습니다.


1-1:

Details

❌ Incorrect review comment

API_BASE 상수 중복 정의 확인이 필요합니다.

검색된 학습 내용에 따르면, API 관련 상수(API_BASE)는 frontend/src/mocks/constants/api.ts에서 import하여 사용해야 하며, 다른 파일에서 재정의하지 말아야 합니다. 클럽 API 전용 상수가 필요한지 확인해 주세요.

기존 API 상수와 중복되는지 확인하기 위해 다음 스크립트를 실행해 주세요:


🏁 Script executed:

#!/bin/bash
# Description: API_BASE 상수가 다른 파일에서도 정의되어 있는지 확인

# API_BASE 상수의 다른 정의 찾기
rg -A 2 -B 2 "API_BASE.*=" --type ts

Length of output: 1537


다음 스크립트로 frontend/src/mocks/constants/api.ts 파일의 존재 여부 및 내용을 확인해 주세요:


🏁 Script executed:

#!/bin/bash
echo "=== frontend/src/mocks/constants/api.ts 파일 존재 및 내용 확인 ==="
if [ -f frontend/src/mocks/constants/api.ts ]; then
  echo "파일이 존재합니다. 내용:"
  sed -n '1,200p' frontend/src/mocks/constants/api.ts
else
  echo "파일이 존재하지 않습니다."
fi

Length of output: 212


아래 스크립트로 frontend/src/mocks/constants 디렉터리의 파일 목록을 확인해 주세요:


🏁 Script executed:

#!/bin/bash
echo "=== frontend/src/mocks/constants 디렉터리 내용 확인 ==="
if [ -d frontend/src/mocks/constants ]; then
  find frontend/src/mocks/constants -maxdepth 1 -type f -printf "FILE: %f\n"
else
  echo "디렉터리가 존재하지 않습니다."
fi

Length of output: 255


API_BASE 중복 정의 없음

현재 frontend/src/mocks/constants 디렉터리에 api.ts 파일이 없으므로, clubApi.ts에서 정의한 API_BASE는 중복이 아니며 유효합니다.
공통 상수 관리가 필요하다면 별도의 api.ts 파일을 생성해 통합 정의하는 방안을 논의해 주세요.

Likely an incorrect or invalid review comment.

frontend/package.json (3)

16-16: Jest 설정 파일 명시가 좋은 개선사항입니다.

커버리지 스크립트에서 Jest 설정 파일을 명시적으로 지정하는 것은 일관성과 명확성을 위한 좋은 개선사항입니다.


29-31: 의존성 배치 확인이 필요합니다.

jest-fixed-jsdommsw가 런타임 의존성(dependencies)에 배치되어 있는데, 이들은 일반적으로 개발/테스트 목적으로만 사용되므로 개발 의존성(devDependencies)에 배치하는 것이 더 적절할 수 있습니다.

이 패키지들이 프로덕션 환경에서도 필요한지 확인해 주세요. 만약 개발/테스트 용도라면 devDependencies로 이동하는 것을 고려해 보세요.


66-75: TypeScript ESLint 설정 업데이트가 적절합니다.

기존 typescript-eslint 패키지를 개별 플러그인과 파서로 분리하고 eslint-config-prettier를 추가한 것은 현대적인 ESLint 설정 패턴에 잘 맞습니다.

frontend/src/mocks/constants/error.ts (1)

1-5: 상수 정의가 적절하게 구현되었습니다.

에러 메시지 상수가 명확하게 정의되었고, as const를 사용하여 타입 안전성을 확보했습니다. 한국어 메시지도 사용자 친화적입니다.

frontend/src/hooks/queries/application/useGetApplication.ts (1)

4-10: React Query 훅이 올바르게 구현되었습니다.

쿼리 키 구조가 적절하고, retry: false 설정도 적절합니다. React Query 베스트 프랙티스를 잘 따르고 있습니다.

frontend/src/hooks/queries/club/useGetClubDetail.ts (1)

11-18: 데이터 변환 로직이 잘 구현되었습니다.

Google Drive URL 변환을 위한 select 함수가 적절하게 추가되었습니다. logo와 feeds 배열에 대한 null 체크와 타입 검증이 적절히 처리되어 있습니다.

frontend/src/mocks/utils/createApiUrl.test.ts (1)

4-18: 테스트 케이스가 포괄적으로 작성되었습니다.

정상 케이스와 에러 케이스를 모두 적절히 테스트하고 있으며, 에러 메시지 검증도 정확합니다. 빈 문자열, 공백, 잘못된 형식 등의 에지 케이스가 잘 다뤄졌습니다.

frontend/src/pages/AdminPage/application/components/QuestionTitle/QuestionTitle.styles.ts (1)

23-23:

Details

❓ Verification inconclusive

브라우저 호환성을 확인하세요.

field-sizing: content 속성은 비교적 최신 CSS 속성으로 일부 브라우저에서 지원되지 않을 수 있습니다.

다음 스크립트로 프로젝트의 브라우저 지원 정책을 확인해보세요:

지원 범위에 따라 폴백 솔루션 검토가 필요할 수 있습니다.


🏁 Script executed:

#!/bin/bash
# 브라우저 지원 정책 확인
if [ -f ".browserslistrc" ]; then
  echo "=== .browserslistrc 내용 ==="
  cat .browserslistrc
elif [ -f "package.json" ]; then
  echo "=== package.json의 browserslist 설정 ==="
  jq '.browserslist' package.json 2>/dev/null || echo "browserslist 설정 없음"
fi

# field-sizing 지원 브라우저 확인 안내
echo ""
echo "field-sizing: content 속성은 Chrome 123+, Firefox 125+ 에서 지원됩니다."
echo "더 넓은 브라우저 지원이 필요하다면 대안을 고려해보세요."

Length of output: 271


browserslist 설정 확인 및 폴백 검토 필요

프로젝트에 .browserslistrcpackage.jsonbrowserslist 설정이 없어 타깃 브라우저 범위를 알 수 없습니다.
field-sizing: content는 Chrome 123+, Firefox 125+에서만 지원되므로 실제 지원 대상에 맞춰 다음을 검토해주세요:

  • 파일: frontend/src/pages/AdminPage/application/components/QuestionTitle/QuestionTitle.styles.ts (23줄)
      field-sizing: content;
    
  • 프로젝트의 browserslist 설정 추가 또는 확인
  • 지원하지 않는 브라우저에 대한 대체 스타일 혹은 폴백 솔루션 도입
frontend/src/pages/MainPage/MainPage.tsx (2)

27-32: 로딩 상태 처리 로직이 잘 구현되었습니다.

isLoadingisEmpty 상태를 명확히 분리하여 사용자 경험을 개선한 점이 좋습니다. 로딩 중, 빈 결과, 데이터 존재 상태를 적절히 구분하고 있습니다.


57-67: 사용자 친화적인 UI 상태 처리가 훌륭합니다.

로딩 스피너, 빈 결과 메시지, 데이터 표시를 명확히 구분한 조건부 렌더링이 사용자 경험을 크게 개선합니다. 특히 빈 결과에 대한 안내 메시지가 친절하고 실용적입니다.

frontend/src/apis/application/updateApplication.ts (1)

5-31: 잘 구현된 API 함수입니다!

  • secureFetch 사용으로 인증 처리가 적절함
  • 에러 핸들링과 로깅이 잘 구현됨
  • 타입 안전성이 보장됨
frontend/src/utils/initSDK.ts (1)

5-16: 좋은 리팩토링입니다!

SDK 초기화 로직의 분리가 잘 되어있고, 환경별 조건 처리도 적절합니다.

frontend/src/hooks/queries/club/useGetCardList.ts (3)

3-4: 타입 정의와 유틸리티 함수 임포트가 적절히 추가되었습니다.

Club 타입 정의와 convertToDriveUrl 유틸리티 함수를 임포트하여 타입 안전성과 데이터 변환 기능을 향상시켰습니다.


12-12: useQuery에 명시적 타입 지정으로 타입 안전성이 향상되었습니다.

useQuery<Club[], unknown, Club[]> 제네릭 타입 지정으로 쿼리 결과의 타입을 명확히 하여 타입 안전성을 개선했습니다.


16-20: select 함수를 통한 데이터 변환 로직이 잘 구현되었습니다.

각 클럽의 로고 URL을 Google Drive URL로 변환하는 로직이 적절히 구현되었습니다. 이렇게 하면 데이터 소스와 UI 표시 간의 일관성을 보장할 수 있습니다.

frontend/src/apis/application/createApplication.ts (3)

1-4: 필요한 의존성들이 적절히 임포트되었습니다.

API 기본 URL, 보안 fetch 함수, 그리고 애플리케이션 폼 데이터 타입이 올바르게 임포트되었습니다.


5-20: API 함수 구조와 요청 설정이 적절합니다.

secureFetch를 사용한 POST 요청 구현이 올바르게 되어있으며, Content-Type 헤더와 JSON 직렬화가 적절히 처리되었습니다.


21-26: 응답 처리 로직이 적절히 구현되었습니다.

HTTP 응답 상태 검증과 JSON 파싱, 그리고 data 필드 반환이 올바르게 구현되었습니다.

frontend/src/pages/AdminPage/application/answer/AnswerApplicationForm.styles.ts (3)

3-10: FormTitle 스타일링이 적절히 구현되었습니다.

적절한 폰트 크기, 굵기, 여백이 설정되어 있어 제목으로서의 역할을 잘 수행할 것 같습니다.


12-16: QuestionsWrapper 레이아웃이 잘 구성되었습니다.

flex 레이아웃과 적절한 gap으로 질문들 사이의 간격이 일관되게 유지됩니다.


18-21: ButtonWrapper 정렬이 적절합니다.

버튼을 우측 정렬로 배치하는 것이 일반적인 폼 UI 패턴과 일치합니다.

frontend/src/mocks/api/apply.ts (5)

1-6: 필요한 의존성들이 적절히 임포트되었습니다.

MSW, 목 데이터, API 베이스, 검증 함수, 에러 메시지 등이 올바르게 임포트되었습니다.


8-13: 클럽 ID 없는 요청에 대한 에러 처리가 적절합니다.

/apply 엔드포인트에 대해 400 에러를 반환하는 것이 적절합니다.


15-33: GET 요청 핸들러가 잘 구현되었습니다.

클럽 ID 검증과 목 데이터 반환이 적절히 처리되었습니다. 응답 구조도 일관성 있게 설계되었습니다.


35-52: POST 요청 핸들러가 적절히 구현되었습니다.

클럽 ID 검증과 성공 메시지 반환이 올바르게 처리되었습니다.


54-72: PUT 요청 핸들러가 적절히 구현되었습니다.

async/await 사용과 클럽 ID 검증, 성공 응답이 올바르게 구현되었습니다.

frontend/src/components/common/InputField/InputField.tsx (1)

19-22: 새로운 props 추가가 잘 구현되었습니다.

readOnly, bgColor, textColor, borderColor props가 적절히 타입 정의되었고, 선택적 props로 설정되어 기존 코드와의 호환성을 보장합니다.

frontend/src/pages/AdminPage/application/fields/ShortText.tsx (1)

7-17: 컴포넌트 props 구조가 잘 설계되었습니다.

TextProps 인터페이스를 사용하여 타입 안정성을 보장하고, 필요한 모든 props를 적절히 받고 있습니다.

frontend/src/pages/AdminPage/application/CreateApplicationForm.tsx (2)

16-17: Early return 패턴이 적절합니다.

clubId가 없을 때 early return으로 처리하는 것이 좋습니다.


89-106: 타입 변경 로직이 잘 구현되었습니다.

choice 타입으로 변경할 때 최소 2개의 아이템을 보장하는 로직이 적절합니다. 타입 안전성도 잘 보장되고 있습니다.

frontend/src/components/common/CustomDropDown/CustomDropDown.styles.ts (2)

8-22: 드롭다운 선택 영역 스타일링이 잘 구현되었습니다.

open 상태에 따른 조건부 스타일링과 부드러운 트랜지션 효과가 잘 적용되어 있습니다. user-select: none 속성으로 텍스트 선택 방지도 적절히 처리되었습니다.


54-60: 아이콘 배치가 적절합니다.

절대 위치 지정과 포인터 이벤트 비활성화가 적절히 처리되어 드롭다운 아이콘이 정확한 위치에 표시되면서 클릭을 방해하지 않습니다.

frontend/src/components/common/CustomTextArea/CustomTextArea.tsx (1)

33-42: 자동 높이 조절 로직이 잘 구현되었습니다.

disabled 상태일 때 높이 조절을 건너뛰는 로직과 value 변경 시 높이를 자동으로 조절하는 기능이 올바르게 구현되어 있습니다.

frontend/src/mocks/api/utils/request.ts (1)

18-26: 편의 함수들이 잘 구현되었습니다.

submitApplication과 updateApplication 함수들이 sendApiRequest를 재사용하여 깔끔하게 구현되어 있습니다. 코드 중복을 줄이는 좋은 패턴입니다.

frontend/src/constants/APPLICATION_FORM.ts (2)

1-22: 상수 정의가 체계적으로 잘 구성되어 있습니다.

각 필드별로 placeholder와 maxLength를 명확하게 정의하여 일관성 있는 UI를 제공할 수 있게 되어 있습니다. as const 사용으로 타입 안전성도 확보했습니다.


34-39: 드롭다운 옵션 생성 로직이 효율적입니다.

DROPDOWN_QUESTION_TYPES 배열을 기반으로 드롭다운 옵션을 동적으로 생성하는 방식이 유지보수성이 좋습니다.

frontend/src/pages/AdminPage/application/components/QuestionAnswerer/QuestionAnswerer.tsx (2)

6-10: Props 인터페이스가 명확하게 정의되었습니다.

Question 타입을 활용하고, onChange 콜백의 시그니처가 명확하게 정의되어 있어 타입 안전성이 확보되었습니다.


17-23: 공통 props 추출이 잘 구현되었습니다.

baseProps로 공통 속성들을 추출하여 코드 중복을 줄이고 일관성을 유지하는 좋은 패턴입니다.

frontend/src/pages/AdminPage/application/components/QuestionTitle/QuestionTitle.tsx (1)

21-21: id 조건 검사를 더 명시적으로 작성하세요

현재 id && <component> 패턴은 id가 0일 때 렌더링되지 않습니다. 의도한 동작인지 확인하세요.

-      {id && <Styled.QuestionTitleId>{id}.</Styled.QuestionTitleId>}
+      {id !== undefined && id !== null && <Styled.QuestionTitleId>{id}.</Styled.QuestionTitleId>}

또는 id가 항상 양수여야 한다면:

-      {id && <Styled.QuestionTitleId>{id}.</Styled.QuestionTitleId>}
+      {id > 0 && <Styled.QuestionTitleId>{id}.</Styled.QuestionTitleId>}
frontend/src/mocks/api/applyHandlers.test.ts (2)

74-84: 테스트 데이터의 일관성을 확인하세요

POST 테스트에서 사용되는 답변 데이터가 문자열과 숫자 키를 혼용하고 있습니다. 실제 API 스펙과 일치하는지 확인이 필요합니다.

      const answers = {
-        '1': ['답변1', '답변2'],
-        '2': ['답변3'],
+        1: ['답변1', '답변2'],
+        2: ['답변3'],
      };

실제 API에서 키가 문자열인지 숫자인지 확인하여 일관성을 맞춰주세요.


1-164: 전반적으로 잘 구성된 테스트 파일입니다

MSW를 활용한 API 테스트가 체계적으로 작성되었으며, 성공과 실패 케이스를 모두 다루고 있습니다. 테스트 구조와 커버리지가 적절합니다.

frontend/public/mockServiceWorker.js (1)

1-308: MSW 생성 파일 - 수정 금지

이 파일은 MSW에서 자동 생성된 파일로, 주석에 명시된 대로 수정하지 마세요. Static analysis 도구의 제안사항도 무시하는 것이 맞습니다.

🧰 Tools
🪛 Biome (1.9.4)

[error] 276-276: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

frontend/src/components/common/InputField/InputField.styles.ts (2)

9-12: 반응형 디자인 개선사항이 우수합니다.

768px 이하에서 min-width를 제거하고 전체 너비를 사용하는 것은 모바일 사용성을 크게 향상시킵니다.


98-101: CharCount 위치 조정이 적절합니다.

CharCount를 input 하단으로 이동하고 폰트 크기를 키운 것은 가독성을 향상시킵니다. 14px에서 16px로 증가한 것도 좋은 개선사항입니다.

frontend/src/components/common/CustomTextArea/CustomTextArea.styles.ts (1)

24-50: TextArea 스타일링이 적절합니다.

overflow: hiddenresize: none 설정으로 동적 높이 조정을 위한 준비가 잘 되어 있고, 포커스 상태의 box-shadow 색상 변경도 좋은 UX를 제공합니다.

frontend/src/pages/AdminPage/application/CreateApplicationForm.styles.ts (1)

3-18: FormTitle 스타일링이 우수합니다.

제목 입력 필드의 큰 폰트 크기(2.5rem)와 placeholder fade 효과가 직관적인 사용자 경험을 제공합니다. 포커스 시 placeholder가 사라지는 애니메이션도 매끄럽습니다.

frontend/src/pages/AdminPage/application/components/QuestionBuilder/QuestionBuilder.tsx (2)

26-28: 타입 검증 로직이 적절합니다.

QUESTION_LABEL_MAP에 없는 타입에 대해 early return으로 null을 반환하는 것은 안전한 접근입니다.


42-90: 질문 타입별 렌더링 로직이 잘 구조화되어 있습니다.

switch문을 사용한 타입별 컴포넌트 렌더링이 명확하고, 각 케이스에서 적절한 props를 전달하고 있습니다. 코드의 가독성과 확장성이 좋습니다.

frontend/src/pages/AdminPage/application/components/QuestionBuilder/QuestionBuilder.styles.ts (2)

11-13: 문제 없음
단순 컨테이너로서 width: 100%만 적용된 부분은 의도에 부합하며 특별한 이슈가 없습니다.


55-61: 스킵: 변경 의도가 명확
단순 래퍼로서 특별한 문제점이 보이지 않습니다.

frontend/src/types/application.ts (1)

1-3: 절대 경로 별칭 설정 검증
@/constants/APPLICATION_FORM가 tsconfig.json의 paths 및 IDE에서 올바르게 인식되는지 확인해주세요.

import { handlers } from './api';
import { RequestHandler } from 'msw/lib/core/handlers/RequestHandler.mjs';

export const worker = setupWorker(...(handlers as unknown as RequestHandler[]));
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

불필요한 이중 타입 캐스팅을 제거하세요.

handlersunknown으로 캐스팅한 후 다시 RequestHandler[]로 캐스팅하는 것은 타입 안전성을 해치고 불필요합니다. handlers가 이미 올바른 타입이라면 직접 사용하거나, 타입 문제가 있다면 근본 원인을 해결해야 합니다.

다음과 같이 수정하는 것을 권장합니다:

-export const worker = setupWorker(...(handlers as unknown as RequestHandler[]));
+export const worker = setupWorker(...handlers);

만약 타입 오류가 발생한다면, handlers의 타입 정의를 확인하고 수정하세요.

📝 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 worker = setupWorker(...(handlers as unknown as RequestHandler[]));
export const worker = setupWorker(...handlers);
🤖 Prompt for AI Agents
In frontend/src/mocks/mswDevSetup.ts at line 5, remove the unnecessary double
type casting of handlers through unknown to RequestHandler[]. Instead, use
handlers directly if it already has the correct type. If a type error occurs,
investigate and fix the type definition of handlers rather than applying unsafe
casts.

Comment on lines +18 to +25
export function initializeChannelService() {
ChannelService.loadScript();
if (process.env.CHANNEL_PLUGIN_KEY) {
ChannelService.boot({
pluginKey: process.env.CHANNEL_PLUGIN_KEY,
});
}
}
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

환경 변수명 일관성 확인 필요

CHANNEL_PLUGIN_KEY의 prefix가 다른 환경변수들과 다릅니다 (REACT_APP_ prefix 없음).

다음 스크립트로 환경변수 사용 패턴을 확인해보세요:


🏁 Script executed:

#!/bin/bash
# 환경변수 사용 패턴 확인
rg "process\.env\." --type tsx --type ts -A 2 -B 2

Length of output: 80


타입 필터 없이 전체 파일에서 환경 변수 사용 패턴을 다시 확인해 보겠습니다.


🏁 Script executed:

#!/bin/bash
# 전체 코드베이스에서 process.env. 사용 패턴 확인
rg "process\.env\." -A 2 -B 2

Length of output: 1843


환경 변수명 일관성 검토 필요
현재 REACT_APP_ prefix를 사용하는 REACT_APP_MIXPANEL_TOKEN과 달리, 채널 플러그인 키(CHANNEL_PLUGIN_KEY), Sentry DSN/Release, API_BASE_URL이 prefix 없이 선언되어 있습니다. CRA 기본 env 주입 규칙(접두사 REACT_APP_ 필요)에 맞추거나, webpack DefinePlugin 설정 여부를 확인해 변수명을 통일해주세요.

  • frontend/src/utils/initSDK.ts
    • process.env.CHANNEL_PLUGIN_KEY → process.env.REACT_APP_CHANNEL_PLUGIN_KEY
    • process.env.SENTRY_DSN → process.env.REACT_APP_SENTRY_DSN
    • process.env.SENTRY_RELEASE → process.env.REACT_APP_SENTRY_RELEASE
  • frontend/src/constants/api.ts
    • process.env.API_BASE_URL → process.env.REACT_APP_API_BASE_URL

.env 파일 및 빌드 설정(webpack DefinePlugin 등)에서 해당 키들이 제대로 주입되는지도 함께 검토 부탁드립니다.

// frontend/src/utils/initSDK.ts
-export function initializeChannelService() {
-  ChannelService.loadScript();
-  if (process.env.CHANNEL_PLUGIN_KEY) {
-    ChannelService.boot({ pluginKey: process.env.CHANNEL_PLUGIN_KEY });
-  }
-}
+export function initializeChannelService() {
+  ChannelService.loadScript();
+  if (process.env.REACT_APP_CHANNEL_PLUGIN_KEY) {
+    ChannelService.boot({ pluginKey: process.env.REACT_APP_CHANNEL_PLUGIN_KEY });
+  }
+}
📝 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 function initializeChannelService() {
ChannelService.loadScript();
if (process.env.CHANNEL_PLUGIN_KEY) {
ChannelService.boot({
pluginKey: process.env.CHANNEL_PLUGIN_KEY,
});
}
}
export function initializeChannelService() {
ChannelService.loadScript();
if (process.env.REACT_APP_CHANNEL_PLUGIN_KEY) {
ChannelService.boot({
pluginKey: process.env.REACT_APP_CHANNEL_PLUGIN_KEY,
});
}
}
🤖 Prompt for AI Agents
In frontend/src/utils/initSDK.ts around lines 18 to 25, the environment variable
CHANNEL_PLUGIN_KEY is used without the REACT_APP_ prefix, which is inconsistent
with other env vars like REACT_APP_MIXPANEL_TOKEN and may cause issues with
Create React App's env injection. Update the code to use
process.env.REACT_APP_CHANNEL_PLUGIN_KEY instead of
process.env.CHANNEL_PLUGIN_KEY. Also verify that the .env file and build
configuration (e.g., webpack DefinePlugin) correctly define and inject this
variable with the REACT_APP_ prefix.

Comment on lines +53 to +56
export interface AnswerItem {
id: number;
answer: string;
}
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

치명적 타입 불일치
ChoiceProps.answerstring | string[]인 반면, AnswerItem.answerstring만 허용합니다.
다지선택 응답을 지원하려면 다음 중 하나를 적용해야 합니다:

  • AnswerItem.answer: string | string[]로 확장
  • 다지선택용 별도 타입 도입
🤖 Prompt for AI Agents
In frontend/src/types/application.ts around lines 53 to 56, the AnswerItem
interface's answer property is typed as string, but ChoiceProps.answer allows
string or string[]. To support multiple-choice answers properly, update
AnswerItem.answer to be string | string[] or create a separate type specifically
for multiple-choice answers that accommodates string arrays.

oesnuj added 2 commits June 6, 2025 15:46
…-MOA-39

[fix] 동아리 SNS 링크 관련 전체 기능 재활성화
…-api-MOA-38

[fix] 클럽 지원서 조회 시 오류 해결 및 Spinner 적용 및 예외 메시지 개선
Copy link
Member

@seongwon030 seongwon030 left a comment

Choose a reason for hiding this comment

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

정말 고생많았습니다 ㅎㅎ 리팩토링도 열심히 해봅시다!

…ication-MOA-40

[fix] 관리자 지원서 작성 기능 배포 전 임시 차단
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: 0

🧹 Nitpick comments (2)
frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx (2)

188-189: 타입 안전성 개선을 고려해보세요.

rawKey as SNSPlatform 타입 단언 대신 더 안전한 접근 방식을 고려해볼 수 있습니다.

-        {Object.entries(SNS_CONFIG).map(([rawKey, { label, placeholder }]) => {
-          const key = rawKey as SNSPlatform;
+        {(Object.keys(SNS_CONFIG) as SNSPlatform[]).map((key) => {
+          const { label, placeholder } = SNS_CONFIG[key];

198-201: 클리어 함수 최적화 제안

개별 클리어 함수들을 하나의 재사용 가능한 함수로 추출하여 코드 중복을 줄일 수 있습니다.

컴포넌트 상단에 헬퍼 함수를 추가:

+  const handleClearSocialLink = (key: SNSPlatform) => {
+    setSocialLinks((prev) => ({ ...prev, [key]: '' }));
+    setSnsErrors((prev) => ({ ...prev, [key]: '' }));
+  };

그리고 onClear 핸들러를 다음과 같이 수정:

-                onClear={() => {
-                  setSocialLinks((prev) => ({ ...prev, [key]: '' }));
-                  setSnsErrors((prev) => ({ ...prev, [key]: '' }));
-                }}
+                onClear={() => handleClearSocialLink(key)}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8ecc61a and 551fe47.

📒 Files selected for processing (6)
  • frontend/src/App.tsx (3 hunks)
  • frontend/src/pages/AdminPage/application/answer/AnswerApplicationForm.tsx (1 hunks)
  • frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx (3 hunks)
  • frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx (1 hunks)
  • frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.styles.ts (2 hunks)
  • frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsx
  • frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx
  • frontend/src/App.tsx
  • frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.styles.ts
  • frontend/src/pages/AdminPage/application/answer/AnswerApplicationForm.tsx
🔇 Additional comments (1)
frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx (1)

186-208: SNS 링크 기능이 잘 구현되었습니다!

동적 렌더링, 실시간 검증, 에러 처리가 모두 적절히 구현되어 있습니다. 특히 다음 부분들이 우수합니다:

  • SNS_CONFIG를 활용한 확장 가능한 동적 렌더링
  • validateSocialLink를 통한 실시간 검증
  • 에러 상태 관리 및 사용자 피드백
  • 개별 필드 클리어 기능

@oesnuj oesnuj merged commit 99036b6 into main Jun 6, 2025
6 checks passed
@tgyuuAn
Copy link

tgyuuAn commented Jun 6, 2025

@oesnuj @seongwon030 빠야!!!!!!!!!!!!!!!!!!!!!!!!

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

Comments