Skip to content

[feature] 사용자가 모집 시작 일정을 확인할 수 있도록 지원하기 버튼을 개선한다#986

Merged
suhyun113 merged 25 commits intodevelop-fefrom
feature/#985-apply-button-countdown-MOA-462
Dec 30, 2025
Merged

[feature] 사용자가 모집 시작 일정을 확인할 수 있도록 지원하기 버튼을 개선한다#986
suhyun113 merged 25 commits intodevelop-fefrom
feature/#985-apply-button-countdown-MOA-462

Conversation

@suhyun113
Copy link
Collaborator

@suhyun113 suhyun113 commented Dec 28, 2025

#️⃣연관된 이슈

ex) #985

📝작업 내용

상세페이지 지원하기 버튼 텍스트를 수정했습니다.

모바일

수정 전

수정 전

수정 후

모집 중 모집 예정(00:00) 모집 예정
이미지1 이미지2 이미지3

모집 중 모집 예정(00:00)
이미지1 이미지2

기존 '모집 전' 버튼을 '모집 시작 날짜'를 추가하여 사용자가 모집 시작 일정을 확인할 수 있도록 했습니다.
모집 예정 또는 모집 마감 상태의 경우 비활성화 상태로 수정했습니다.
버튼의 날짜는 시간과 분이 0인지에 따라 보이도록 했습니다.

카카오톡 공유하기 버튼과 지원하기 버튼 디자인을 수정했습니다.
기존의 pretendard 폰트가 적용되지 않는 문제를 해결했고, 변경된 디자인으로 여백과 크기를 조정했습니다.

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

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

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

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

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

🫡 참고사항

Summary by CodeRabbit

  • 새로운 기능

    • 모바일 전용 공유 아이콘 추가로 모바일에서 더 적합한 공유 UI 제공
  • 개선

    • 모집 상태(모집 전/진행/종료/상시)를 기반해 마감 문구를 더 정확히 표시(모집 시작/모집 마감/상시 등)
    • 모집 예정·종료 시 신청 버튼을 비활성화하고 비활성화 시 배경색·커서·크기 등 시각적 피드백 강화
    • 하단 영역·공유 버튼의 반응형 스타일과 폼 요소(입력·버튼·셀렉트·텍스트영역)의 글꼴/패딩 조정

✏️ Tip: You can customize this high-level summary in your review settings.

@suhyun113 suhyun113 self-assigned this Dec 28, 2025
@suhyun113 suhyun113 added ✨ Feature 기능 개발 💻 FE Frontend labels Dec 28, 2025
@vercel
Copy link

vercel bot commented Dec 28, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
moadong Ready Ready Preview, Comment Dec 30, 2025 11:58am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 28, 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.
  • You can also validate your configuration using the online YAML validator.
  • 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

모집 상태(recruitmentStatus)를 도입해 데드라인 텍스트 로직과 Footer/Apply 버튼의 렌더링·비활성화 조건, 타입·유틸·테스트·스타일·아이콘 분기를 상태 기반으로 변경했습니다.

Changes

Cohort / File(s) 변경 사항
Apply 버튼 스타일
frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.styles.ts
테마 색상·미디어 유틸 도입; 높이 50→60px; 텍스트 색상 변경; cursor·background-colordisabled에 따라 조건부 적용; 모바일 브레이크포인트를 테마 기반으로 전환
Apply 버튼 로직
frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
내부 상수 제거 후 clubDetail.recruitmentStatusisRecruitmentClosed/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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7516776 and a8c637d.

📒 Files selected for processing (3)
  • frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.styles.ts
  • frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
  • frontend/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 with if/else or 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.ts
  • frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.styles.ts
  • frontend/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.ts
  • frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.styles.ts
  • frontend/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하고 있으며, 프로젝트의 기존 의존성을 활용하고 있습니다.

Comment on lines 90 to 91
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 | 🟠 Major

매직 스트링을 상수로 추출하여 컴포넌트 간 결합도를 낮추세요.

'모집 시작' 문자열이 하드코딩되어 있어 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.

Comment on lines 14 to 16
Copy link
Contributor

@coderabbitai coderabbitai bot Dec 28, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

시간 포맷 로직에 오류가 있습니다.

현재 로직은 분(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.

Copy link
Contributor

Choose a reason for hiding this comment

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

이건 0시 표시하는게 더자연스러울듯하네요,

Copy link
Contributor

Choose a reason for hiding this comment

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

@lepitaaar 알겠습니다! 한국어 표기에서는 "0시 30분"이 더 자연스럽다는 의견 반영하겠습니다. 현재 로직을 유지하는 것으로 하겠습니다. 👍

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 | 🟠 Major

매직 스트링을 공유 상수로 추출하세요.

" 모집 시작" 문자열이 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.

@suhyun113 suhyun113 changed the title 사용자가 모집 시작 일정을 확인할 수 있도록 지원하기 버튼을 개선한다 [feature] 사용자가 모집 시작 일정을 확인할 수 있도록 지원하기 버튼을 개선한다 Dec 28, 2025
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/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.

📥 Commits

Reviewing files that changed from the base of the PR and between 5cd7348 and 46679ea.

⛔ Files ignored due to path filters (2)
  • frontend/src/assets/images/icons/share_icon.svg is excluded by !**/*.svg
  • frontend/src/assets/images/icons/share_icon_mobile.svg is excluded by !**/*.svg
📒 Files selected for processing (5)
  • frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.styles.ts
  • frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.styles.ts
  • frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.styles.ts
  • frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx
  • frontend/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 with if/else or 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.ts
  • frontend/src/styles/Global.styles.ts
  • frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx
  • frontend/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.ts
  • frontend/src/styles/Global.styles.ts
  • frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx
  • frontend/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.tsx
  • frontend/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

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.

지원하기 버튼 분기 처리하느라 수고하셨습니다 !!

Comment on lines 17 to 20
Copy link
Member

Choose a reason for hiding this comment

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

코드래빗 코멘트처럼 "모집 시작"을 상수로 분리하면 좋을 것 같습니다

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

넵!

Copy link
Collaborator Author

@suhyun113 suhyun113 Dec 30, 2025

Choose a reason for hiding this comment

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

clubApplyButton에서 판단 기준을 변경하면서 상수는 아예 제거하고, getDeadLineText에서 상수 분리했습니당

793eb83 3f1ef4c ad8c113

Comment on lines 10 to 18
Copy link
Member

Choose a reason for hiding this comment

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

출력형식이 바뀌면서 테스트코드가 하나 실패하네요 확인 후 수정 부탁드릴게요~

Copy link
Collaborator Author

@suhyun113 suhyun113 Dec 30, 2025

Choose a reason for hiding this comment

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

수정했슴니당

26c680d 0214657

Copy link
Member

Choose a reason for hiding this comment

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

변경해주신거 잘 봤습니다 ! 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('모집 마감');
  });
});

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

선언되어있던 기간에는 시간 정보가 없어서 00:00인 경우와 구분이 안 된다는걸 생각하지 못 했네요...
알려주셔서 감사합니다!!

816adf4 295a91c

Copy link
Contributor

@lepitaaar lepitaaar left a comment

Choose a reason for hiding this comment

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

수고하셨습니다. 다만 지금코드가 deadlinetext의 문자와 강결합되어있어, deadlinetext로 모집상태를 분류하기보단 clubdetail의 recruitmentstatus 사용해서 구분해주는게 더좋아보입니다. 이렇게 변경가능할까요?

Comment on lines 14 to 16
Copy link
Contributor

Choose a reason for hiding this comment

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

이건 0시 표시하는게 더자연스러울듯하네요,

@suhyun113
Copy link
Collaborator Author

suhyun113 commented Dec 30, 2025

수고하셨습니다. 다만 지금코드가 deadlinetext의 문자와 강결합되어있어, deadlinetext로 모집상태를 분류하기보단 clubdetail의 recruitmentstatus 사용해서 구분해주는게 더좋아보입니다. 이렇게 변경가능할까요?

저도 그렇게 변경하는게 좋을 것 같아 변경하던 중이었습니다!
테스트 하던 중 아래와 같은 버그를 발견하여 고치게되었습니당

'모집 시작 전이고 모집 예정이 아닌 동아리의 경우 모집마감으로 처리되어야하는데, 모집 예정 날짜가 나타나는 버그'를 발견했습니다.

수정 전 수정 후
이미지1 이미지2

현재 지원하기 버튼의 텍스트 판단 기준이 DeadLineText로 모집 시작 기간과 모집 마감 기간으로 되어있어서 생기는 문제였습니다.
모집 마감 기간이 지난 동아리의 경우만 모집 마감으로 처리되고 있어서 모집 전이지만 모집 예정이 아닌 동아리도 모집 마감으로 되어야하는데 안 되고 있었습니다.
이 문제를 해결하기 위해서는 판단 기준을 clubDetailrecruitmentStatus로 하는 게 좋은 것 같아 그렇게 수정했습니다!

3f1ef4c

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: 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.recruitmentStatusRecruitmentStatus 타입을 사용하지만, 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 46679ea and ad8c113.

📒 Files selected for processing (6)
  • frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx
  • frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
  • frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx
  • frontend/src/types/club.ts
  • frontend/src/utils/getDeadLineText.test.ts
  • frontend/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 with if/else or 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.tsx
  • frontend/src/utils/getDeadLineText.test.ts
  • frontend/src/types/club.ts
  • frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx
  • frontend/src/utils/getDeadLineText.ts
  • frontend/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.tsx
  • frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx
  • frontend/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.tsx
  • frontend/src/utils/getDeadLineText.test.ts
  • frontend/src/types/club.ts
  • frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx
  • frontend/src/utils/getDeadLineText.ts
  • frontend/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.ts
  • frontend/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.ts
  • frontend/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.ts
  • frontend/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.ts
  • frontend/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!

recruitmentStatus prop을 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 상수로 모집 상태 텍스트를 정의하여 관리가 용이해졌습니다.

과거 리뷰에서 공유 상수 파일로 추출하는 것이 권장되었으나, 현재 구현에서는 ClubApplyButtonrecruitmentStatus를 직접 사용하도록 변경되어 문자열 비교가 더 이상 필요하지 않으므로, 현재 구조로도 충분합니다.

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

📥 Commits

Reviewing files that changed from the base of the PR and between ad8c113 and 0214657.

📒 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 with if/else or 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 날짜를 전달하고 '모집 마감'을 기대하는 것은 올바른 동작입니다.

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.

리뷰 추가요

Copy link
Member

Choose a reason for hiding this comment

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

goWithForm보다 navigateToApplicationForm 은 어떤가요?

Copy link
Collaborator Author

@suhyun113 suhyun113 Dec 30, 2025

Choose a reason for hiding this comment

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

말씀해주신 네이밍이 함수의 역할과 맥락을 훨씬 잘 드러내는 것 같아여ㅕ
단순 동작보다 도메인과 라우팅 의도가 명확해져서 가독성도 좋아진다고 느꼈습니다ㅏ 반영할게요! 감사합니당
841ebb5

Copy link
Member

Choose a reason for hiding this comment

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

이런 주석도 TODO나 개발자가 조심해야 하는 부분이 아니라면 제거해도 좋을 것 같네요

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

저는 그동안 제가 헷깔리지 않도록 주석을 작성해두었는데 보다보니 불필요한 주석이 많아진 것 같네요
좋은 코드는 주석 없이도 읽혀야하는 것을 간과하고 있었던 것 같아요 반영하겠습니다ㅏ

주석이 없어도 쉽게 이해가 되도록 변수명, 함수명 등을 수정했습니다. 확인해주세요~
99461a2

Copy link
Member

Choose a reason for hiding this comment

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

훨씬 좋은 것 같습니다 👍

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0214657 and 99461a2.

📒 Files selected for processing (3)
  • frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
  • frontend/src/utils/getDeadLineText.test.ts
  • frontend/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 with if/else or 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/src/utils/getDeadLineText.ts
  • frontend/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.ts
  • frontend/src/utils/getDeadLineText.ts
  • frontend/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.ts
  • frontend/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.ts
  • frontend/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.ts
  • frontend/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.ts
  • frontend/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 상수를 함수와 같은 파일에 정의하여 결합도를 낮추고 유지보수성을 높였습니다. 이전 리뷰에서 권고된 매직 스트링 추출이 잘 반영되었습니다.

@seongwon030 seongwon030 self-requested a review December 30, 2025 12:25
Copy link
Contributor

@lepitaaar lepitaaar left a comment

Choose a reason for hiding this comment

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

수고하셧어요

@suhyun113 suhyun113 merged commit a87e2a6 into develop-fe Dec 30, 2025
3 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Dec 30, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💻 FE Frontend ✨ Feature 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

Comments