[feature] 사용자가 모집 시작 일정을 확인할 수 있도록 지원하기 버튼을 개선한다#986
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning
|
| Cohort / File(s) | 변경 사항 |
|---|---|
Apply 버튼 스타일 frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.styles.ts |
테마 색상·미디어 유틸 도입; 높이 50→60px; 텍스트 색상 변경; cursor·background-color를 disabled에 따라 조건부 적용; 모바일 브레이크포인트를 테마 기반으로 전환 |
Apply 버튼 로직 frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx |
내부 상수 제거 후 clubDetail.recruitmentStatus로 isRecruitmentClosed/isRecruitmentUpcoming/isAlwaysRecruiting 파생; 모달·내비게이션 상태명 변경; UPCOMING/CLOSED 시 버튼 비활성화 및 표시 텍스트 분기 변경 |
데드라인 유틸 & 테스트 frontend/src/utils/getDeadLineText.ts frontend/src/utils/getDeadLineText.test.ts |
getDeadlineText 시그니처에 recruitmentStatus 추가; CLOSED/UPCOMING/ALWAYS 조기 반환 로직 도입; date-fns 포맷·로케일 사용 및 테스트 케이스 갱신 |
타입 변경 frontend/src/types/club.ts |
RecruitmentStatus 유니온 타입 추가(`'OPEN' |
Footer: props·스타일 연동 frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.styles.ts |
ClubDetailFooter에 recruitmentStatus 전달 및 props 추가; getDeadlineText 호출에 recruitmentStatus 전달; 스타일에 media 유틸 도입, z-index·패딩·모바일 패딩 조정 |
공유 버튼 스타일·아이콘 frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.styles.ts frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx |
아이콘 크기 50→60px(모바일 44px)로 조정; media 유틸 도입; 모바일에서 ShareIconMobile 사용 분기 추가 |
글로벌 스타일 확장 frontend/src/styles/Global.styles.ts |
font-family 적용 범위를 textarea에서 textarea, button, input, select로 확장 |
Sequence Diagram(s)
sequenceDiagram
autonumber
participant Page as ClubDetailPage
participant Footer as ClubDetailFooter
participant Button as ClubApplyButton
participant Util as getDeadLineText
Note over Page,Footer: 초기 렌더링
Page->>Footer: pass clubDetail.recruitmentStatus
Footer->>Util: getDeadlineText(start,end,recruitmentStatus,today)
Util-->>Footer: deadlineText (CLOSED/UPCOMING/ALWAYS/D-#)
Footer-->>Page: render footer with deadlineText
Page->>Button: pass clubDetail.recruitmentStatus
Button->>Button: derive isClosed/isUpcoming/isAlways
alt isUpcoming or isClosed
Button-->>UI: render disabled button + deadlineText
else OPEN
Button-->>UI: render active button (click → modal/navigation)
end
Estimated code review effort
🎯 3 (Moderate) | ⏱️ ~20 minutes
Possibly related issues
- [feature] MOA-462 사용자가 모집 시작 일정을 확인할 수 있도록 지원하기 버튼을 개선한다 #985: Apply 버튼의 모집 시작/카운트다운 및 상태 기반 비활성화와 동일 영역을 다루므로 관련됩니다.
- Moadong/moadong#984: 모집 시작 텍스트 표시 및 버튼 비활성화 목표와 직접적으로 일치합니다.
Possibly related PRs
- [HotFix] 동아리 상세페이지 모집 버튼 푸터 삭제 복구 #978: ClubApplyButton·getDeadLineText·Footer 관련 변경이 겹치며 코드 레벨 연관성이 높습니다.
- [feature] 관리자 페이지 모집기간 선택에 상시모집 처리 버튼을 추가한다 #774: 상시 모집(ALWAYS) 처리 도입과 관련된 변경이 겹칩니다.
- [feature] 동아리 상시모집 상태 추가 #707: ALWAYS 상태 및 표시 관련 수정과 연관됩니다.
Suggested labels
✅ Test, 🛠Fix
Suggested reviewers
- seongwon030
- oesnuj
- lepitaaar
- Zepelown
Pre-merge checks and finishing touches
✅ Passed checks (5 passed)
| Check name | Status | Explanation |
|---|---|---|
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
| Title check | ✅ Passed | 제목은 지원하기 버튼 개선을 통해 사용자가 모집 시작 일정을 확인할 수 있도록 지원한다는 주요 변경사항을 명확하게 설명합니다. |
| Linked Issues check | ✅ Passed | PR 변경사항은 모집 상태 기반 버튼 개선, 모집 시작 일정 조건부 표기, 버튼 활성/비활성화 처리 등 MOA-462의 요구사항을 모두 충족합니다. |
| Out of Scope Changes check | ✅ Passed | 디자인 개선(폰트, 크기, 여백), 스타일 정규화(Global.styles.ts) 등 모든 변경사항이 버튼 개선 목표와 직접 관련되어 있습니다. |
| Docstring Coverage | ✅ Passed | No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. |
✨ 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.
Comment @coderabbitai help to get the list of available commands and usage tips.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.styles.ts (1)
21-21: 삼항 연산자 포맷팅 일관성 개선을 고려하세요.삼항 연산자의 콜론 앞뒤 공백이 일관되지 않습니다. Line 19와 동일하게 콜론 뒤에도 공백을 추가하는 것이 좋습니다.
🔎 포맷팅 개선 제안
- background-color: ${({ disabled }) => disabled ? colors.gray[400] : colors.primary[800]}; + background-color: ${({ disabled }) => (disabled ? colors.gray[400] : colors.primary[800])};
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.styles.tsfrontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsxfrontend/src/utils/getDeadLineText.ts
🧰 Additional context used
📓 Path-based instructions (3)
frontend/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (frontend/.cursorrules)
frontend/**/*.{ts,tsx,js,jsx}: Replace magic numbers with named constants for clarity
Replace complex/nested ternaries withif/elseor IIFEs for readability
Assign complex boolean conditions to named variables for explicit meaning
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle)
Use unique and descriptive names for custom wrappers/functions to avoid ambiguity
Define constants near related logic or ensure names link them clearly to avoid silent failures
Break down broad state management into smaller, focused hooks/contexts to reduce coupling
Files:
frontend/src/utils/getDeadLineText.tsfrontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.styles.tsfrontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
frontend/**/*.{ts,tsx}
📄 CodeRabbit inference engine (frontend/.cursorrules)
Use consistent return types for similar functions/hooks
Files:
frontend/src/utils/getDeadLineText.tsfrontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.styles.tsfrontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
frontend/**/*.{tsx,jsx}
📄 CodeRabbit inference engine (frontend/.cursorrules)
frontend/**/*.{tsx,jsx}: Abstract complex logic/interactions into dedicated 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 when using form libraries like react-hook-form
Use Component Composition instead of Props Drilling to reduce coupling
Files:
frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
🧠 Learnings (2)
📚 Learning: 2025-11-25T14:08:23.253Z
Learnt from: CR
Repo: Moadong/moadong PR: 0
File: frontend/.cursorrules:0-0
Timestamp: 2025-11-25T14:08:23.253Z
Learning: Applies to frontend/**/*.{tsx,jsx} : Separate significantly different conditional UI/logic into distinct components
Applied to files:
frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
📚 Learning: 2025-09-21T02:23:27.796Z
Learnt from: seongwon030
Repo: Moadong/moadong PR: 744
File: frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx:47-48
Timestamp: 2025-09-21T02:23:27.796Z
Learning: ClubApplyButton 컴포넌트에서 ShareButton은 항상 렌더링되어야 하므로 정적 import를 사용하는 것이 적절함. 동적 import는 불필요함.
Applied to files:
frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
🧬 Code graph analysis (1)
frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.styles.ts (1)
frontend/src/styles/theme/colors.ts (1)
colors(1-81)
🔇 Additional comments (4)
frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.styles.ts (1)
2-2: 테마 색상 import 및 disabled 상태 처리가 적절합니다.테마의 colors를 import하여 일관된 색상 시스템을 사용하고 있으며, disabled 상태에 따라 cursor를 적절하게 처리하고 있습니다.
Also applies to: 19-19
frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx (2)
93-113: 버튼 콘텐츠 렌더링 로직이 명확합니다.상태별로 early return을 사용하여 가독성이 좋으며, 조건부 렌더링이 명확하게 구분되어 있습니다.
118-120: 비활성화 로직이 올바르게 구현되었습니다.모집 예정 또는 마감 상태에서 버튼을 비활성화하여 사용자 상호작용을 적절히 제어하고 있습니다. PR 목표와 정확히 일치합니다.
frontend/src/utils/getDeadLineText.ts (1)
1-2: date-fns import 및 한국어 로케일 사용이 적절합니다.날짜 포맷팅을 위한
format함수와 한국어 로케일을 올바르게 import하고 있으며, 프로젝트의 기존 의존성을 활용하고 있습니다.
There was a problem hiding this comment.
매직 스트링을 상수로 추출하여 컴포넌트 간 결합도를 낮추세요.
'모집 시작' 문자열이 하드코딩되어 있어 getDeadLineText.ts의 출력 형식과 정확히 일치해야 합니다. 이는 두 파일 간의 강한 결합을 만들며, 한쪽에서 형식이 변경되면 다른 쪽에서 조용히 실패할 수 있습니다.
코딩 가이드라인에 따라 상수를 관련 로직 근처에 정의하거나 이름으로 명확하게 연결하여 조용한 실패를 방지해야 합니다.
🔎 상수 추출 제안
RECRUITMENT_STATUS 객체에 추가:
const RECRUITMENT_STATUS = {
ALWAYS: '상시 모집',
CLOSED: '모집 마감',
+ UPCOMING_SUFFIX: '모집 시작',
};그리고 사용 부분을 수정:
- const isUpcoming = deadlineText?.includes('모집 시작');
+ const isUpcoming = deadlineText?.includes(RECRUITMENT_STATUS.UPCOMING_SUFFIX);더 나은 방법으로는, getDeadLineText.ts에서도 동일한 상수를 import하여 사용하도록 공통 상수 파일로 추출하는 것을 고려하세요.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
around lines 90-91, the literal '모집 시작' is used as a magic string causing tight
coupling with getDeadLineText; extract this literal into a named constant
(either add a new key to RECRUITMENT_STATUS or move to a shared constants
module), replace the inline string with that constant in this file, and update
getDeadLineText to import and use the same constant so both producers and
consumers share the single source of truth.
There was a problem hiding this comment.
시간 포맷 로직에 오류가 있습니다.
현재 로직은 분(minute)이 0이 아닌 경우 항상 시간(hour)을 포함하여 표시합니다. 이로 인해 자정 이후의 시간(예: 00:30)이 "12월 25일 0시 30분"으로 표시되어 사용자에게 혼란을 줄 수 있습니다.
조건부 포맷 문자열 구성 로직을 수정하여 시간이 0일 때는 표시하지 않도록 해야 합니다.
🔎 수정된 로직 제안
const hour = recruitmentStart.getHours();
const minute = recruitmentStart.getMinutes();
let formatStr = 'M월 d일';
- if (hour !== 0) formatStr = 'M월 d일 H시';
- if (minute !== 0) formatStr = 'M월 d일 H시 m분';
+ if (minute !== 0) {
+ formatStr = hour !== 0 ? 'M월 d일 H시 m분' : 'M월 d일 m분';
+ } else if (hour !== 0) {
+ formatStr = 'M월 d일 H시';
+ }
return `${format(recruitmentStart, formatStr, { locale: ko })} 모집 시작`;이렇게 수정하면:
- 시간과 분이 모두 있을 때: "12월 25일 13시 30분 모집 시작"
- 시간만 있을 때: "12월 25일 13시 모집 시작"
- 분만 있을 때: "12월 25일 30분 모집 시작"
- 둘 다 없을 때: "12월 25일 모집 시작"
🤖 Prompt for AI Agents
In frontend/src/utils/getDeadLineText.ts around lines 14 to 16, the current
format logic always forces an hour into the string when minute !== 0, causing
e.g. "0시 30분" for 00:30; change the conditional composition so hour and minute
are appended independently: start with 'M월 d일', append ' H시' only if hour !== 0,
append ' m분' only if minute !== 0; this yields correct outputs for hour+minute,
hour-only, minute-only, and neither.
There was a problem hiding this comment.
@lepitaaar 알겠습니다! 한국어 표기에서는 "0시 30분"이 더 자연스럽다는 의견 반영하겠습니다. 현재 로직을 유지하는 것으로 하겠습니다. 👍
There was a problem hiding this comment.
매직 스트링을 공유 상수로 추출하세요.
" 모집 시작" 문자열이 ClubApplyButton.tsx에서 상태 판별에 사용되고 있습니다. 이 문자열이 변경되면 컴포넌트의 동작이 조용히 실패할 수 있습니다.
공통 상수 파일(예: constants/recruitment.ts)에 추출하여 두 파일에서 import하여 사용하는 것을 권장합니다.
🔎 공유 상수 추출 제안
새 파일 생성: frontend/src/constants/recruitment.ts
export const RECRUITMENT_TEXT = {
ALWAYS: '상시 모집',
CLOSED: '모집 마감',
UPCOMING_SUFFIX: ' 모집 시작',
} as const;getDeadLineText.ts에서:
+import { RECRUITMENT_TEXT } from '@/constants/recruitment';
- return `${format(recruitmentStart, formatStr, { locale: ko })} 모집 시작`;
+ return `${format(recruitmentStart, formatStr, { locale: ko })}${RECRUITMENT_TEXT.UPCOMING_SUFFIX}`;ClubApplyButton.tsx에서:
+import { RECRUITMENT_TEXT } from '@/constants/recruitment';
const RECRUITMENT_STATUS = {
- ALWAYS: '상시 모집',
- CLOSED: '모집 마감',
+ ...RECRUITMENT_TEXT,
};
- const isUpcoming = deadlineText?.includes('모집 시작');
+ const isUpcoming = deadlineText?.includes(RECRUITMENT_TEXT.UPCOMING_SUFFIX);Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In frontend/src/utils/getDeadLineText.ts around line 18, the literal " 모집 시작" is
a magic string used by ClubApplyButton.tsx to determine state; extract shared
recruitment text constants into a new file frontend/src/constants/recruitment.ts
(e.g., an exported RECRUITMENT_TEXT object with UPCOMING_SUFFIX), replace the
inline `" 모집 시작"` in getDeadLineText.ts with an import of that constant, and
update ClubApplyButton.tsx to import and use the same constant so both files
rely on the single source of truth.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.styles.ts (1)
8-21: 패딩 및 z-index 값을 명명된 상수로 추출하는 것을 고려해보세요.스타일 값들(z-index: 1040, padding: 24px/16px 등)이 하드코딩되어 있습니다. 이러한 값들을 명명된 상수로 정의하면 다음과 같은 이점이 있습니다:
- 의미 전달: 예를 들어
FOOTER_Z_INDEX = 1040,FOOTER_PADDING_BOTTOM_DESKTOP = 24- 유지보수성: 여러 곳에서 같은 값을 사용할 경우 일관성 유지 용이
- TODO 코멘트의 z-index 조정 시에도 한 곳만 수정하면 됨
Based on learnings: 관련 로직 근처에 상수를 정의하거나 이름으로 명확히 연결되도록 하여 조용한 실패를 방지하세요.
🔎 명명된 상수 사용 예시
+const FOOTER_Z_INDEX = 1040; // TODO: Portal로 모달 분리 후 header보다 낮게 재조정 +const FOOTER_PADDING_DESKTOP = '10px 0px 24px 0px'; +const FOOTER_PADDING_MOBILE = '10px 0px 16px 0px'; + export const ClubDetailFooterContainer = styled.div` position: sticky; bottom: 0; width: 100%; - z-index: 1040; // TODO: Portal로 모달 분리 후 header보다 낮게 재조정 - padding: 10px 0px 24px 0px; + z-index: ${FOOTER_Z_INDEX}; + padding: ${FOOTER_PADDING_DESKTOP}; display: flex; align-items: center; justify-content: space-between; gap: 16px; background-color: white; border-top: 1px solid #cdcdcd; ${media.mobile} { - padding: 10px 0px 16px 0px; + padding: ${FOOTER_PADDING_MOBILE}; } `;frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.styles.ts (1)
11-17: 아이콘 크기 값을 명명된 상수로 정의하는 것을 고려해보세요.아이콘의 width/height 값(60px, 44px)이 하드코딩되어 있습니다. 이를 명명된 상수로 추출하면:
- 의미 전달 개선:
SHARE_ICON_SIZE_DESKTOP = 60,SHARE_ICON_SIZE_MOBILE = 44- 유지보수 용이: 크기 변경 시 한 곳만 수정
- 일관성: 다른 곳에서 같은 크기를 사용할 경우 재사용 가능
반응형 패턴(
media.mobile사용)은 다른 컴포넌트들과 일관되게 잘 적용되었습니다.🔎 명명된 상수 사용 예시
import { media } from '@/styles/mediaQuery'; import styled from 'styled-components'; +const SHARE_ICON_SIZE_DESKTOP = 60; +const SHARE_ICON_SIZE_MOBILE = 44; + export const ShareButtonContainer = styled.div` display: flex; justify-content: flex-end; cursor: pointer; `; export const ShareButtonIcon = styled.img` - width: 60px; - height: 60px; + width: ${SHARE_ICON_SIZE_DESKTOP}px; + height: ${SHARE_ICON_SIZE_DESKTOP}px; ${media.mobile} { - width: 44px; - height: 44px; + width: ${SHARE_ICON_SIZE_MOBILE}px; + height: ${SHARE_ICON_SIZE_MOBILE}px; } `;
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (2)
frontend/src/assets/images/icons/share_icon.svgis excluded by!**/*.svgfrontend/src/assets/images/icons/share_icon_mobile.svgis excluded by!**/*.svg
📒 Files selected for processing (5)
frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.styles.tsfrontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.styles.tsfrontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.styles.tsfrontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsxfrontend/src/styles/Global.styles.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.styles.ts
🧰 Additional context used
📓 Path-based instructions (3)
frontend/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (frontend/.cursorrules)
frontend/**/*.{ts,tsx,js,jsx}: Replace magic numbers with named constants for clarity
Replace complex/nested ternaries withif/elseor IIFEs for readability
Assign complex boolean conditions to named variables for explicit meaning
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle)
Use unique and descriptive names for custom wrappers/functions to avoid ambiguity
Define constants near related logic or ensure names link them clearly to avoid silent failures
Break down broad state management into smaller, focused hooks/contexts to reduce coupling
Files:
frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.styles.tsfrontend/src/styles/Global.styles.tsfrontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsxfrontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.styles.ts
frontend/**/*.{ts,tsx}
📄 CodeRabbit inference engine (frontend/.cursorrules)
Use consistent return types for similar functions/hooks
Files:
frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.styles.tsfrontend/src/styles/Global.styles.tsfrontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsxfrontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.styles.ts
frontend/**/*.{tsx,jsx}
📄 CodeRabbit inference engine (frontend/.cursorrules)
frontend/**/*.{tsx,jsx}: Abstract complex logic/interactions into dedicated 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 when using form libraries like react-hook-form
Use Component Composition instead of Props Drilling to reduce coupling
Files:
frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx
🧠 Learnings (3)
📚 Learning: 2025-11-25T14:08:23.253Z
Learnt from: CR
Repo: Moadong/moadong PR: 0
File: frontend/.cursorrules:0-0
Timestamp: 2025-11-25T14:08:23.253Z
Learning: Applies to frontend/**/*.{ts,tsx,js,jsx} : Define constants near related logic or ensure names link them clearly to avoid silent failures
Applied to files:
frontend/src/styles/Global.styles.ts
📚 Learning: 2025-09-21T02:23:27.796Z
Learnt from: seongwon030
Repo: Moadong/moadong PR: 744
File: frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx:47-48
Timestamp: 2025-09-21T02:23:27.796Z
Learning: ClubApplyButton 컴포넌트에서 ShareButton은 항상 렌더링되어야 하므로 정적 import를 사용하는 것이 적절함. 동적 import는 불필요함.
Applied to files:
frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsxfrontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.styles.ts
📚 Learning: 2025-03-19T05:18:07.818Z
Learnt from: seongwon030
Repo: Moadong/moadong PR: 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/ClubDetailPage/components/ShareButton/ShareButton.tsx
🧬 Code graph analysis (2)
frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.styles.ts (1)
frontend/src/styles/mediaQuery.ts (1)
media(8-14)
frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.styles.ts (1)
frontend/src/styles/mediaQuery.ts (1)
media(8-14)
🔇 Additional comments (2)
frontend/src/styles/Global.styles.ts (1)
9-11: 폼 컨트롤 전체에 일관된 폰트 적용 - 좋습니다!
textarea에만 적용되던 Pretendard 폰트를 모든 폼 컨트롤(button,input,select)로 확장하여 일관된 타이포그래피를 제공합니다. 이는 사용자 경험 향상에 기여합니다.frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx (1)
2-2: 모바일 디바이스 감지 및 조건부 아이콘 렌더링 - 잘 구현되었습니다!
useDevice훅을 사용하여 모바일 환경을 감지하고 적절한 공유 아이콘을 선택하는 로직이 깔끔하게 구현되었습니다. 간단한 삼항 연산자 사용으로 가독성도 좋고, 기존 공유 기능을 유지하면서 모바일 UX를 개선합니다.Also applies to: 6-6, 18-18, 60-63
seongwon030
left a comment
There was a problem hiding this comment.
지원하기 버튼 분기 처리하느라 수고하셨습니다 !!
There was a problem hiding this comment.
코드래빗 코멘트처럼 "모집 시작"을 상수로 분리하면 좋을 것 같습니다
There was a problem hiding this comment.
출력형식이 바뀌면서 테스트코드가 하나 실패하네요 확인 후 수정 부탁드릴게요~
There was a problem hiding this comment.
변경해주신거 잘 봤습니다 ! recruitmentStart와 recruitmentEnd가 상수로 선언되어 있어 모든 테스트 케이스에서 공유되고 있었네요.
이러면 new Date('2025-04-01')은 시간정보가 없어서 자동으로 00:00:00이 되고, 결국 시간이 09:00인 경우와 00:00인 경우를 구분할 수 없게 되겠네요.
각 테스트를 분리해서 recruitmentStart와 recruitmentEnd를 직접 포함시키는게 더 나을 것 같아요..!
import getDeadlineText from './getDeadLineText';
describe('getDeadlineText 함수 테스트', () => {
it.each([
'오늘이 모집 종료일인 경우',
new Date('2025-04-01'),
new Date('2025-04-10'),
'2025-04-10',
'OPEN',
'D-Day',
],
[
'모집 종료일까지 5일 남은 경우',
new Date('2025-04-01'),
new Date('2025-04-10'),
'2025-04-05',
'OPEN',
'D-5',
],
[
'오늘이 모집 종료일 이후인 경우',
new Date('2025-04-01'),
new Date('2025-04-10'),
'2025-04-11',
'CLOSED',
'모집 마감',
],
[
'모집 시작일이 아직 남은 경우 (시간 포함)',
new Date('2025-04-01T09:00:00'),
new Date('2025-04-10'),
'2025-03-30',
'UPCOMING',
'4월 1일 09:00 모집 시작',
],
[
'모집 시작 시간이 00:00인 경우',
new Date('2025-04-01T00:00:00'),
new Date('2025-04-10'),
'2025-03-30',
'UPCOMING',
'4월 1일 모집 시작',
],
])(
'%s',
(
_,
recruitmentStart,
recruitmentEnd,
todayStr,
recruitmentStatus,
expected,
) => {
const today = new Date(todayStr);
expect(
getDeadlineText(
recruitmentStart,
recruitmentEnd,
recruitmentStatus,
today,
),
).toBe(expected);
},
);
it('모집 기간이 null인 경우 모집 마감을 반환해야 한다', () => {
expect(getDeadlineText(null, null, 'CLOSED')).toBe('모집 마감');
});
});
lepitaaar
left a comment
There was a problem hiding this comment.
수고하셨습니다. 다만 지금코드가 deadlinetext의 문자와 강결합되어있어, deadlinetext로 모집상태를 분류하기보단 clubdetail의 recruitmentstatus 사용해서 구분해주는게 더좋아보입니다. 이렇게 변경가능할까요?
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
frontend/src/utils/getDeadLineText.test.ts (1)
14-16: 테스트에서recruitmentStatus파라미터가 누락되었습니다.
getDeadlineText함수 시그니처가recruitmentStatus파라미터를 추가로 받도록 변경되었지만, 테스트에서는 이 파라미터를 전달하지 않고 있습니다. 이로 인해 테스트가 실패하거나 예상치 못한 동작이 발생할 수 있습니다.🔎 테스트 수정 제안
describe('getDeadlineText 함수 테스트', () => { it.each([ - ['오늘이 모집 종료일인 경우', '2025-04-10', 'D-Day'], - ['모집 종료일까지 5일 남은 경우', '2025-04-05', 'D-5'], - ['오늘이 모집 종료일 이후인 경우', '2025-04-11', '모집 마감'], - ['모집 시작일이 아직 남은 경우', '2025-03-30', '4월 1일 09:00 모집 시작'], - ])('%s', (_, todayStr, expected) => { + ['오늘이 모집 종료일인 경우', '2025-04-10', 'OPEN', 'D-Day'], + ['모집 종료일까지 5일 남은 경우', '2025-04-05', 'OPEN', 'D-5'], + ['오늘이 모집 종료일 이후인 경우', '2025-04-11', 'CLOSED', '모집 마감'], + ['모집 시작일이 아직 남은 경우', '2025-03-30', 'UPCOMING', '4월 1일 09:00 모집 시작'], + ])('%s', (_, todayStr, status, expected) => { const today = new Date(todayStr); - expect(getDeadlineText(recruitmentStart, recruitmentEnd, today)).toBe( + expect(getDeadlineText(recruitmentStart, recruitmentEnd, status, today)).toBe( expected, ); });
🧹 Nitpick comments (3)
frontend/src/types/club.ts (1)
82-82:ClubApiResponse.recruitmentStatus타입을RecruitmentStatus로 변경하는 것을 권장합니다.
Club.recruitmentStatus는RecruitmentStatus타입을 사용하지만,ClubApiResponse.recruitmentStatus는 여전히string타입입니다. API 응답을Club타입으로 변환할 때 타입 불일치가 발생할 수 있습니다.🔎 타입 일관성 개선 제안
export interface ClubApiResponse { id: string; name: string; logo: string; cover: string; tags: string[]; state: string; introduction: string; description: DetailedDescription; recruitmentPeriod: string; - recruitmentStatus: string; + recruitmentStatus: RecruitmentStatus; externalApplicationUrl: string; socialLinks: Record<SNSPlatform, string>; category: string; division: string; }frontend/src/utils/getDeadLineText.ts (2)
13-13:recruitmentStatus파라미터 타입을RecruitmentStatus로 변경하세요.
string대신RecruitmentStatus타입을 사용하면 타입 안전성이 향상됩니다.🔎 타입 개선 제안
+import { RecruitmentStatus } from '@/types/club'; const getDeadlineText = ( recruitmentStart: Date | null, recruitmentEnd: Date | null, - recruitmentStatus: string, + recruitmentStatus: RecruitmentStatus, today: Date = new Date(), ): string => {
39-39: 매직 넘버 365를 명명된 상수로 추출하세요.코딩 가이드라인에 따라 매직 넘버는 명명된 상수로 정의하여 의미를 명확히 해야 합니다.
🔎 상수 추출 제안
+const ALWAYS_RECRUITMENT_THRESHOLD_DAYS = 365; + const RECRUITMENT_STATUS = { CLOSED: '모집 마감', ALWAYS: '상시 모집', UPCOMING: '모집 시작', }; // ... 함수 내부에서 - if (days > 365) return RECRUITMENT_STATUS.ALWAYS; + if (days > ALWAYS_RECRUITMENT_THRESHOLD_DAYS) return RECRUITMENT_STATUS.ALWAYS;코딩 가이드라인에 따라 매직 넘버를 명명된 상수로 대체합니다.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (6)
frontend/src/pages/ClubDetailPage/ClubDetailPage.tsxfrontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsxfrontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsxfrontend/src/types/club.tsfrontend/src/utils/getDeadLineText.test.tsfrontend/src/utils/getDeadLineText.ts
🧰 Additional context used
📓 Path-based instructions (3)
frontend/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (frontend/.cursorrules)
frontend/**/*.{ts,tsx,js,jsx}: Replace magic numbers with named constants for clarity
Replace complex/nested ternaries withif/elseor IIFEs for readability
Assign complex boolean conditions to named variables for explicit meaning
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle)
Use unique and descriptive names for custom wrappers/functions to avoid ambiguity
Define constants near related logic or ensure names link them clearly to avoid silent failures
Break down broad state management into smaller, focused hooks/contexts to reduce coupling
Files:
frontend/src/pages/ClubDetailPage/ClubDetailPage.tsxfrontend/src/utils/getDeadLineText.test.tsfrontend/src/types/club.tsfrontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsxfrontend/src/utils/getDeadLineText.tsfrontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
frontend/**/*.{tsx,jsx}
📄 CodeRabbit inference engine (frontend/.cursorrules)
frontend/**/*.{tsx,jsx}: Abstract complex logic/interactions into dedicated 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 when using form libraries like react-hook-form
Use Component Composition instead of Props Drilling to reduce coupling
Files:
frontend/src/pages/ClubDetailPage/ClubDetailPage.tsxfrontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsxfrontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
frontend/**/*.{ts,tsx}
📄 CodeRabbit inference engine (frontend/.cursorrules)
Use consistent return types for similar functions/hooks
Files:
frontend/src/pages/ClubDetailPage/ClubDetailPage.tsxfrontend/src/utils/getDeadLineText.test.tsfrontend/src/types/club.tsfrontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsxfrontend/src/utils/getDeadLineText.tsfrontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
🧠 Learnings (8)
📚 Learning: 2025-11-25T14:08:23.253Z
Learnt from: CR
Repo: Moadong/moadong PR: 0
File: frontend/.cursorrules:0-0
Timestamp: 2025-11-25T14:08:23.253Z
Learning: Applies to frontend/**/*.{ts,tsx,js,jsx} : Define constants near related logic or ensure names link them clearly to avoid silent failures
Applied to files:
frontend/src/utils/getDeadLineText.tsfrontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
📚 Learning: 2025-11-25T14:08:23.253Z
Learnt from: CR
Repo: Moadong/moadong PR: 0
File: frontend/.cursorrules:0-0
Timestamp: 2025-11-25T14:08:23.253Z
Learning: Applies to frontend/**/*.{ts,tsx,js,jsx} : Replace magic numbers with named constants for clarity
Applied to files:
frontend/src/utils/getDeadLineText.tsfrontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
📚 Learning: 2025-08-24T17:33:23.092Z
Learnt from: suhyun113
Repo: Moadong/moadong PR: 698
File: frontend/src/hooks/queries/applicants/useDeleteApplicants.ts:10-16
Timestamp: 2025-08-24T17:33:23.092Z
Learning: The codebase has inconsistent console logging patterns across files - mixing console.log/console.error for errors, using both Korean and English messages, and various formatting styles (template literals vs separate arguments, with/without emojis). A unified logging format like `console.error('[ModuleName] Operation failed:', error)` should be established for better debugging consistency.
Applied to files:
frontend/src/utils/getDeadLineText.tsfrontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
📚 Learning: 2025-07-19T05:05:10.196Z
Learnt from: seongwon030
Repo: Moadong/moadong PR: 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/utils/getDeadLineText.tsfrontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
📚 Learning: 2025-11-25T14:08:23.253Z
Learnt from: CR
Repo: Moadong/moadong PR: 0
File: frontend/.cursorrules:0-0
Timestamp: 2025-11-25T14:08:23.253Z
Learning: Applies to frontend/**/*.{tsx,jsx} : Colocate simple, localized logic or use inline definitions to reduce context switching
Applied to files:
frontend/src/utils/getDeadLineText.ts
📚 Learning: 2025-11-25T14:08:23.253Z
Learnt from: CR
Repo: Moadong/moadong PR: 0
File: frontend/.cursorrules:0-0
Timestamp: 2025-11-25T14:08:23.253Z
Learning: Applies to frontend/**/*.{tsx,jsx} : Separate significantly different conditional UI/logic into distinct components
Applied to files:
frontend/src/utils/getDeadLineText.ts
📚 Learning: 2025-03-19T05:18:07.818Z
Learnt from: seongwon030
Repo: Moadong/moadong PR: 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/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
📚 Learning: 2025-09-21T02:23:27.796Z
Learnt from: seongwon030
Repo: Moadong/moadong PR: 744
File: frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx:47-48
Timestamp: 2025-09-21T02:23:27.796Z
Learning: ClubApplyButton 컴포넌트에서 ShareButton은 항상 렌더링되어야 하므로 정적 import를 사용하는 것이 적절함. 동적 import는 불필요함.
Applied to files:
frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
🧬 Code graph analysis (1)
frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx (2)
frontend/src/types/club.ts (1)
RecruitmentStatus(3-7)frontend/src/utils/recruitmentDateParser.ts (1)
recruitmentDateParser(3-18)
🔇 Additional comments (7)
frontend/src/types/club.ts (1)
3-7: LGTM! 타입 정의가 명확합니다.
RecruitmentStatus유니온 타입이 잘 정의되어 있어 모집 상태를 타입 안전하게 관리할 수 있습니다.frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (1)
83-87: LGTM!
recruitmentStatusprop을ClubDetailFooter에 올바르게 전달하고 있습니다. 변경이 깔끔하고 명확합니다.frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx (2)
85-88: 상태 플래그 도출 로직이 개선되었습니다.
recruitmentStatus에서 직접 상태 플래그를 도출하는 방식이 기존 문자열 비교 방식보다 명확하고 유지보수하기 좋습니다. 다만, 위에서 언급한 대로 이 코드 블록을handleClick함수 정의 전으로 이동해야 합니다.
111-113: 버튼 비활성화 로직이 적절합니다.모집 예정(
isUpcoming) 또는 모집 마감(isClosed) 상태일 때 버튼을 비활성화하는 것이 UX 관점에서 올바른 처리입니다.frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx (2)
5-10: LGTM! 타입 안전한 props 정의입니다.
RecruitmentStatus타입을 import하고 props 인터페이스에 적용하여 타입 안전성을 확보했습니다.
18-22:getDeadlineText호출이 새 시그니처와 올바르게 연동됩니다.
recruitmentStatus를 세 번째 인자로 전달하여 상태 기반 텍스트 생성이 가능해졌습니다.frontend/src/utils/getDeadLineText.ts (1)
4-8: 상수 정의가 적절합니다.
RECRUITMENT_STATUS상수로 모집 상태 텍스트를 정의하여 관리가 용이해졌습니다.과거 리뷰에서 공유 상수 파일로 추출하는 것이 권장되었으나, 현재 구현에서는
ClubApplyButton이recruitmentStatus를 직접 사용하도록 변경되어 문자열 비교가 더 이상 필요하지 않으므로, 현재 구조로도 충분합니다.
frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
frontend/src/utils/getDeadLineText.test.ts (1)
3-4: 테스트 픽스처의 시간대 의존성 문제
new Date('2025-04-01')형식은 UTC 자정으로 파싱되어 로컬 시간대에 따라 다른 시간이 됩니다. KST에서는 09:00, 다른 시간대에서는 다른 결과가 나올 수 있어 테스트가 환경에 따라 실패할 수 있습니다.명시적인 로컬 시간을 사용하는 것이 좋습니다:
🔎 제안된 수정
-const recruitmentStart = new Date('2025-04-01'); -const recruitmentEnd = new Date('2025-04-10'); +const recruitmentStart = new Date(2025, 3, 1, 9, 0); // 4월 1일 09:00 로컬 시간 +const recruitmentEnd = new Date(2025, 3, 10, 23, 59); // 4월 10일 23:59 로컬 시간
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
frontend/src/utils/getDeadLineText.test.ts
🧰 Additional context used
📓 Path-based instructions (2)
frontend/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (frontend/.cursorrules)
frontend/**/*.{ts,tsx,js,jsx}: Replace magic numbers with named constants for clarity
Replace complex/nested ternaries withif/elseor IIFEs for readability
Assign complex boolean conditions to named variables for explicit meaning
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle)
Use unique and descriptive names for custom wrappers/functions to avoid ambiguity
Define constants near related logic or ensure names link them clearly to avoid silent failures
Break down broad state management into smaller, focused hooks/contexts to reduce coupling
Files:
frontend/src/utils/getDeadLineText.test.ts
frontend/**/*.{ts,tsx}
📄 CodeRabbit inference engine (frontend/.cursorrules)
Use consistent return types for similar functions/hooks
Files:
frontend/src/utils/getDeadLineText.test.ts
🔇 Additional comments (1)
frontend/src/utils/getDeadLineText.test.ts (1)
20-22: LGTM!null 입력에 대한 테스트 케이스가 적절합니다.
'CLOSED'상태와 함께 null 날짜를 전달하고'모집 마감'을 기대하는 것은 올바른 동작입니다.
There was a problem hiding this comment.
goWithForm보다 navigateToApplicationForm 은 어떤가요?
There was a problem hiding this comment.
말씀해주신 네이밍이 함수의 역할과 맥락을 훨씬 잘 드러내는 것 같아여ㅕ
단순 동작보다 도메인과 라우팅 의도가 명확해져서 가독성도 좋아진다고 느꼈습니다ㅏ 반영할게요! 감사합니당
841ebb5
There was a problem hiding this comment.
이런 주석도 TODO나 개발자가 조심해야 하는 부분이 아니라면 제거해도 좋을 것 같네요
There was a problem hiding this comment.
저는 그동안 제가 헷깔리지 않도록 주석을 작성해두었는데 보다보니 불필요한 주석이 많아진 것 같네요
좋은 코드는 주석 없이도 읽혀야하는 것을 간과하고 있었던 것 같아요 반영하겠습니다ㅏ
주석이 없어도 쉽게 이해가 되도록 변수명, 함수명 등을 수정했습니다. 확인해주세요~
99461a2
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
frontend/src/utils/getDeadLineText.ts (1)
16-16: 디버그용console.log를 제거해주세요.프로덕션 코드에 디버그용 로그가 남아있습니다.
🔎 수정 제안
- console.log(recruitmentStart, recruitmentEnd, recruitmentStatus, today);
🧹 Nitpick comments (4)
frontend/src/utils/getDeadLineText.test.ts (1)
72-81: 들여쓰기가 파일의 나머지 부분과 일치하지 않습니다.마지막 테스트 블록의 들여쓰기가 다른 테스트들과 다릅니다. 일관성을 위해 동일한 들여쓰기 스타일을 유지해주세요.
🔎 들여쓰기 수정 제안
- it('모집 중 상태인데 모집 종료일까지 1년 이상 남으면 상시 모집을 반환해야 한다', () => { - expect( - getDeadlineText( - new Date('2025-01-01'), - new Date('2027-01-01'), - 'OPEN', - new Date('2025-01-01'), - ), - ).toBe('상시 모집'); -}); + it('모집 중 상태인데 모집 종료일까지 1년 이상 남으면 상시 모집을 반환해야 한다', () => { + expect( + getDeadlineText( + new Date('2025-01-01'), + new Date('2027-01-01'), + 'OPEN', + new Date('2025-01-01'), + ), + ).toBe('상시 모집'); + });frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx (1)
75-78: 에러 발생 시 빈 모달을 표시하는 것이 사용자 경험에 좋지 않을 수 있습니다.지원서 옵션 조회 실패 시 빈 옵션 배열로 모달을 여는 대신, 사용자에게 오류 메시지를 표시하는 것이 더 나을 수 있습니다.
🔎 에러 처리 개선 제안
} catch (e) { - setApplicationOptions([]); - setIsApplicationModalOpen(true); console.error('지원서 옵션 조회 중 오류가 발생했습니다.', e); + alert('지원서 목록을 불러오는 중 오류가 발생했습니다. 다시 시도해주세요.'); }frontend/src/utils/getDeadLineText.ts (2)
13-13:recruitmentStatus파라미터에 유니온 타입 사용을 권장합니다.
string대신RecruitmentStatus타입 또는 리터럴 유니온을 사용하면 타입 안전성이 향상됩니다.🔎 타입 개선 제안
+type RecruitmentStatusType = 'OPEN' | 'CLOSED' | 'UPCOMING' | 'ALWAYS'; + const getDeadlineText = ( recruitmentStart: Date | null, recruitmentEnd: Date | null, - recruitmentStatus: string, + recruitmentStatus: RecruitmentStatusType, today: Date = new Date(), ): string => {또는
frontend/src/types/club.ts에서RecruitmentStatus를 import하여 사용할 수 있습니다.
37-37: 매직 넘버 365를 명명된 상수로 추출하세요.코딩 가이드라인에 따라 매직 넘버를 명명된 상수로 정의하면 의미가 더 명확해집니다. Based on coding guidelines.
🔎 상수 추출 제안
+const ALWAYS_RECRUITING_THRESHOLD_DAYS = 365; + const RECRUITMENT_STATUS = { CLOSED: '모집 마감', ALWAYS: '상시 모집', UPCOMING: '모집 시작', }; // ... 함수 내부에서: - if (days > 365) return RECRUITMENT_STATUS.ALWAYS; + if (days > ALWAYS_RECRUITING_THRESHOLD_DAYS) return RECRUITMENT_STATUS.ALWAYS;
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsxfrontend/src/utils/getDeadLineText.test.tsfrontend/src/utils/getDeadLineText.ts
🧰 Additional context used
📓 Path-based instructions (3)
frontend/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (frontend/.cursorrules)
frontend/**/*.{ts,tsx,js,jsx}: Replace magic numbers with named constants for clarity
Replace complex/nested ternaries withif/elseor IIFEs for readability
Assign complex boolean conditions to named variables for explicit meaning
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle)
Use unique and descriptive names for custom wrappers/functions to avoid ambiguity
Define constants near related logic or ensure names link them clearly to avoid silent failures
Break down broad state management into smaller, focused hooks/contexts to reduce coupling
Files:
frontend/src/utils/getDeadLineText.test.tsfrontend/src/utils/getDeadLineText.tsfrontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
frontend/**/*.{ts,tsx}
📄 CodeRabbit inference engine (frontend/.cursorrules)
Use consistent return types for similar functions/hooks
Files:
frontend/src/utils/getDeadLineText.test.tsfrontend/src/utils/getDeadLineText.tsfrontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
frontend/**/*.{tsx,jsx}
📄 CodeRabbit inference engine (frontend/.cursorrules)
frontend/**/*.{tsx,jsx}: Abstract complex logic/interactions into dedicated 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 when using form libraries like react-hook-form
Use Component Composition instead of Props Drilling to reduce coupling
Files:
frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
🧠 Learnings (9)
📚 Learning: 2025-11-25T14:08:23.253Z
Learnt from: CR
Repo: Moadong/moadong PR: 0
File: frontend/.cursorrules:0-0
Timestamp: 2025-11-25T14:08:23.253Z
Learning: Applies to frontend/**/*.{ts,tsx,js,jsx} : Define constants near related logic or ensure names link them clearly to avoid silent failures
Applied to files:
frontend/src/utils/getDeadLineText.tsfrontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
📚 Learning: 2025-11-25T14:08:23.253Z
Learnt from: CR
Repo: Moadong/moadong PR: 0
File: frontend/.cursorrules:0-0
Timestamp: 2025-11-25T14:08:23.253Z
Learning: Applies to frontend/**/*.{ts,tsx,js,jsx} : Replace magic numbers with named constants for clarity
Applied to files:
frontend/src/utils/getDeadLineText.tsfrontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
📚 Learning: 2025-08-24T17:33:23.092Z
Learnt from: suhyun113
Repo: Moadong/moadong PR: 698
File: frontend/src/hooks/queries/applicants/useDeleteApplicants.ts:10-16
Timestamp: 2025-08-24T17:33:23.092Z
Learning: The codebase has inconsistent console logging patterns across files - mixing console.log/console.error for errors, using both Korean and English messages, and various formatting styles (template literals vs separate arguments, with/without emojis). A unified logging format like `console.error('[ModuleName] Operation failed:', error)` should be established for better debugging consistency.
Applied to files:
frontend/src/utils/getDeadLineText.tsfrontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
📚 Learning: 2025-07-19T05:05:10.196Z
Learnt from: seongwon030
Repo: Moadong/moadong PR: 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/utils/getDeadLineText.tsfrontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
📚 Learning: 2025-11-25T14:08:23.253Z
Learnt from: CR
Repo: Moadong/moadong PR: 0
File: frontend/.cursorrules:0-0
Timestamp: 2025-11-25T14:08:23.253Z
Learning: Applies to frontend/**/*.{tsx,jsx} : Colocate simple, localized logic or use inline definitions to reduce context switching
Applied to files:
frontend/src/utils/getDeadLineText.ts
📚 Learning: 2025-11-25T14:08:23.253Z
Learnt from: CR
Repo: Moadong/moadong PR: 0
File: frontend/.cursorrules:0-0
Timestamp: 2025-11-25T14:08:23.253Z
Learning: Applies to frontend/**/*.{tsx,jsx} : Separate significantly different conditional UI/logic into distinct components
Applied to files:
frontend/src/utils/getDeadLineText.ts
📚 Learning: 2025-05-19T05:45:52.957Z
Learnt from: lepitaaar
Repo: Moadong/moadong PR: 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/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
📚 Learning: 2025-03-19T05:18:07.818Z
Learnt from: seongwon030
Repo: Moadong/moadong PR: 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/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
📚 Learning: 2025-09-30T05:26:41.788Z
Learnt from: alsdddk
Repo: Moadong/moadong PR: 765
File: backend/src/main/java/moadong/club/service/ClubApplyService.java:431-435
Timestamp: 2025-09-30T05:26:41.788Z
Learning: In the Moadong codebase's club application feature (backend/src/main/java/moadong/club/), multiple ClubApplicationForm entities can have ACTIVE status for the same clubId, semesterYear, and semesterTerm simultaneously. There is no uniqueness constraint requiring only one ACTIVE form per semester.
Applied to files:
frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
🧬 Code graph analysis (1)
frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx (2)
frontend/src/types/application.ts (1)
ApplicationForm(65-68)frontend/src/constants/eventName.ts (1)
USER_EVENT(1-38)
🔇 Additional comments (4)
frontend/src/utils/getDeadLineText.test.ts (1)
5-44: 테스트 케이스 구조 개선이 잘 되었습니다!이전에 지적된 문제(동일한
recruitmentStart로 다른 출력을 기대하는 모순)가 해결되었습니다. 각 테스트 케이스가 고유한 Date 객체를 사용하여 시간 포함(09:00)과 00:00 케이스를 명확히 구분합니다.frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx (2)
82-86: recruitmentStatus 기반 로직 전환이 잘 되었습니다!PR 리뷰 코멘트에서 권고된 대로
deadlineText문자열 의존 대신clubDetail.recruitmentStatus를 사용하여 상태를 판단하도록 변경되었습니다. 이로써getDeadLineText.ts와의 강결합이 해소되었습니다.
108-110: 버튼 비활성화 조건이 명확합니다.
UPCOMING또는CLOSED상태에서 버튼을 비활성화하는 로직이 요구사항에 맞게 구현되었습니다.frontend/src/utils/getDeadLineText.ts (1)
4-8: 상수 정의가 관련 로직 근처에 잘 배치되었습니다.
RECRUITMENT_STATUS상수를 함수와 같은 파일에 정의하여 결합도를 낮추고 유지보수성을 높였습니다. 이전 리뷰에서 권고된 매직 스트링 추출이 잘 반영되었습니다.


#️⃣연관된 이슈
📝작업 내용
모바일
수정 전
수정 후
웹
기존 '모집 전' 버튼을 '모집 시작 날짜'를 추가하여 사용자가 모집 시작 일정을 확인할 수 있도록 했습니다.
모집 예정 또는 모집 마감 상태의 경우 비활성화 상태로 수정했습니다.
버튼의 날짜는 시간과 분이 0인지에 따라 보이도록 했습니다.
카카오톡 공유하기 버튼과 지원하기 버튼 디자인을 수정했습니다.
기존의 pretendard 폰트가 적용되지 않는 문제를 해결했고, 변경된 디자인으로 여백과 크기를 조정했습니다.
중점적으로 리뷰받고 싶은 부분(선택)
논의하고 싶은 부분(선택)
🫡 참고사항
Summary by CodeRabbit
새로운 기능
개선
✏️ Tip: You can customize this high-level summary in your review settings.