-
Notifications
You must be signed in to change notification settings - Fork 2
[HotFix] 동아리 상세페이지 모집 버튼 푸터 삭제 복구 #978
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
[fix] 소개할게요 필드 introDescription 으로 변경
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning
|
| 코호트 / 파일(s) | 요약 |
|---|---|
페이지 통합 frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx |
ClubDetailFooter를 import 후 렌더링; recruitmentStart/recruitmentEnd props 전달 |
푸터 컴포넌트 frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx, .../ClubDetailFooter.styles.ts |
새 컴포넌트: 날짜 파싱(recruitmentDateParser), getDeadlineText 계산 후 ClubApplyButton에 deadlineText 전달; sticky footer 스타일 추가 |
지원 버튼 컴포넌트 frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx, .../ClubApplyButton.styles.ts |
새 컴포넌트: 모집 종료 체크, 지원 폼 옵션 조회, 내부/외부 네비게이션, 모달 선택 로직; 관련 스타일(ApplyButtonContainer, ApplyButton, Separator) 추가 |
공유 버튼 컴포넌트 frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx, .../ShareButton.styles.ts |
새 컴포넌트: Kakao SDK 기반 공유 로직 및 스타일(ShareButtonContainer, ShareButtonIcon) 추가 |
유틸 변경 frontend/src/utils/recruitmentDateParser.ts, frontend/src/utils/recruitmentDateParser.test.ts |
반환 타입을 `Date |
타입 변경 frontend/src/types/club.ts |
ClubDetail의 recruitmentStart, recruitmentEnd를 `string |
Sequence Diagram(s)
sequenceDiagram
participant User
participant ClubDetailPage
participant ClubDetailFooter
participant ClubApplyButton
participant API as FormAPI
participant Router
participant Kakao as KakaoSDK
User->>ClubDetailPage: 페이지 방문
ClubDetailPage->>ClubDetailFooter: recruitmentStart, recruitmentEnd 전달
ClubDetailFooter->>ClubDetailFooter: recruitmentDateParser -> getDeadlineText
ClubDetailFooter->>ClubApplyButton: deadlineText 전달
rect rgba(230,240,250,0.9)
Note over User,ClubApplyButton: 지원 흐름
User->>ClubApplyButton: 지원 버튼 클릭
ClubApplyButton->>ClubApplyButton: 모집 종료 여부 확인
alt 모집 종료됨
ClubApplyButton->>User: 알림 표시
else 모집 중
ClubApplyButton->>API: 지원 옵션 조회
API-->>ClubApplyButton: 옵션 목록
alt 옵션 1개
ClubApplyButton->>Router: 내부 폼 네비게이션 또는 외부 URL 오픈
else 다수 옵션
ClubApplyButton->>User: 모달 오픈
User->>ClubApplyButton: 옵션 선택
ClubApplyButton->>Router: 선택된 폼으로 이동 / 외부 URL 오픈
end
end
end
rect rgba(240,230,250,0.9)
Note over User,Kakao: 공유 흐름
User->>ClubDetailFooter: 공유 버튼 클릭
ClubDetailFooter->>Kakao: Kakao.Share.sendDefault 호출
Kakao->>User: 공유 UI 표시
end
Estimated code review effort
🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
- [feature] 관리자페이지 수정하기 api 연동 #195 — 동일한
ClubDetail타입(recruitmentStart/recruitmentEnd) 변경과 관련된 PR입니다. - [feature] 상세 페이지 푸터 디자인 변경 #795 — ClubDetailPage 푸터·버튼 관련 UI/스타일 변경과 중복되는 컴포넌트 수정이 있습니다.
- [feature] 동아리 상세페이지 디자인 개편에 따른 최종 구현 통합 및 API 연동 #972 — ClubDetailPage의 푸터 및 지원/공유 흐름을 변경한 다른 PR로 코드 레벨 충돌 가능성이 있습니다.
Suggested reviewers
- seongwon030
- oesnuj
워크스루
클럽 상세 페이지에 새로운 ClubDetailFooter 컴포넌트를 추가하여 모집 기간을 파싱한 후 지원 버튼과 공유 버튼을 렌더링합니다. 모집 날짜 파서의 반환 타입을 Date | null로 변경하고 ClubDetail 인터페이스의 모집 필드를 필수 속성으로 업데이트합니다.
변경사항
| 코호트 / 파일 | 요약 |
|---|---|
ClubDetailPage 통합 frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx |
ClubDetailFooter 컴포넌트를 추가로 임포트하고 렌더링하며, 모집 시작/종료 날짜를 props로 전달 |
ClubDetailFooter 컴포넌트 frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx, .../ClubDetailFooter.styles.ts |
새로운 함수형 컴포넌트로 모집 날짜를 파싱하여 deadline 텍스트를 계산한 후 ClubApplyButton에 전달; sticky 푸터 스타일 추가 |
ClubApplyButton 컴포넌트 frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx, .../ClubApplyButton.styles.ts |
새로운 컴포넌트로 지원 모달/선택 로직, 모집 종료 확인, 지원 폼 옵션 조회 및 내부/외부 네비게이션 처리; 스타일 정의 추가 (ApplyButtonContainer, ApplyButton, Separator) |
ShareButton 컴포넌트 frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx, .../ShareButton.styles.ts |
새로운 컴포넌트로 Kakao SDK를 사용한 클럽 공유 기능 구현; 스타일 정의 추가 (ShareButtonContainer, ShareButtonIcon) |
타입 정의 업데이트 frontend/src/types/club.ts |
ClubDetail 인터페이스의 recruitmentStart, recruitmentEnd를 `string |
모집 날짜 파서 유틸리티 frontend/src/utils/recruitmentDateParser.ts, frontend/src/utils/recruitmentDateParser.test.ts |
반환 타입을 `Date |
시퀀스 다이어그램
sequenceDiagram
participant User
participant ClubDetailPage
participant ClubDetailFooter
participant ClubApplyButton
participant Modal as ApplicationSelectModal
participant API as Form API
participant Router
participant Kakao as Kakao SDK
User->>ClubDetailPage: 클럽 상세 페이지 방문
ClubDetailPage->>ClubDetailFooter: recruitmentStart/End 전달
ClubDetailFooter->>ClubDetailFooter: recruitmentDateParser로 파싱
ClubDetailFooter->>ClubDetailFooter: getDeadlineText 계산
ClubDetailFooter->>ClubApplyButton: deadlineText 전달
rect rgb(230, 240, 250)
Note over User,ClubApplyButton: 지원 버튼 클릭 흐름
User->>ClubApplyButton: 지원 버튼 클릭
ClubApplyButton->>ClubApplyButton: 모집 종료 여부 확인
alt 모집 종료됨
ClubApplyButton->>User: 알림 표시
else 모집 중
ClubApplyButton->>API: 지원 폼 옵션 조회
API-->>ClubApplyButton: 옵션 반환
alt 옵션 1개
ClubApplyButton->>Router: 폼 페이지로 직접 이동 또는 외부 URL 열기
else 옵션 2개 이상
ClubApplyButton->>Modal: 모달 오픈
User->>Modal: 옵션 선택
Modal->>Router: 선택된 폼으로 이동 또는 외부 URL 열기
end
end
end
rect rgb(240, 230, 250)
Note over User,Kakao: 공유 버튼 클릭 흐름
User->>ClubApplyButton: 공유 버튼 클릭
ClubApplyButton->>Kakao: Kakao.Share.sendDefault 호출
Kakao->>User: 공유 메뉴 표시
end
예상 코드 리뷰 노력
🎯 3 (Moderate) | ⏱️ ~25분
관련된 가능성 있는 PR
- PR
#195: ClubDetail 타입(recruitmentStart/recruitmentEnd) 변경 관련 수정사항이 포함되어 있음. - PR
#795: ClubDetailPage 푸터 및 관련 컴포넌트(ApplyButton, ShareButton) 스타일/구현과 중복 가능. - PR
#972: ClubDetailPage 컴포넌트(푸터/지원 흐름)와 유사한 변경을 포함하여 충돌 가능성 있음.
권장 검토자
- oesnuj
- seongwon030
Pre-merge checks and finishing touches
✅ Passed checks (3 passed)
| Check name | Status | Explanation |
|---|---|---|
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
| Title check | ✅ Passed | PR 제목이 주요 변경 사항과 직접 관련이 있으며, 삭제된 동아리 상세페이지 모집 버튼 푸터를 복구했다는 핵심 내용을 명확하게 요약합니다. |
| Docstring Coverage | ✅ Passed | No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. |
✨ Finishing touches
- 📝 Generate docstrings
🧪 Generate unit tests (beta)
- Create PR with unit tests
- Post copyable unit tests in a comment
- Commit unit tests in branch
fix/delete-clubdetailfooter
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Nitpick comments (6)
frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx (1)
19-19: 명시적으로null을 반환하도록 개선하세요.조기 반환 시 암묵적으로
undefined를 반환하고 있습니다. React 컴포넌트에서는 명시적으로null을 반환하는 것이 모범 사례입니다.🔎 권장 수정사항
- if (!clubDetail) return; + if (!clubDetail) return null;frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx (5)
26-26: non-null 단언 대신 타입 가드 이후 사용 패턴을 명확히 하세요.Line 32에서
clubId를 체크하고 있기 때문에 line 26의 non-null 단언(clubId!)은 불필요합니다. 훅 호출 순서 규칙을 준수하면서도 타입 안정성을 높이려면,clubId가 undefined일 수 있는 경우 early return 이전에 빈 문자열 등으로 기본값을 제공하거나, enabled 옵션을 활용하는 것이 더 안전한 패턴입니다.🔎 더 안전한 패턴 제안
const { clubId } = useParams<{ clubId: string }>(); const navigate = useNavigate(); const trackEvent = useMixpanelTrack(); - const { data: clubDetail } = useGetClubDetail(clubId!); + const { data: clubDetail } = useGetClubDetail(clubId || ''); // 모달 옵션 상태 const [isOpen, setIsOpen] = useState(false); const [options, setOptions] = useState<ApplicationForm[]>([]); if (!clubId || !clubDetail) return null;
35-54: 복잡한 로직을 분리하여 단일 책임 원칙을 준수하세요.
goWithForm함수가 여러 책임을 가지고 있습니다:
- 폼 데이터 조회
- 외부/내부 폼 모드 판단
- 외부 URL 처리 (window.open)
- 내부 라우팅 처리 (navigate)
- 모달 상태 업데이트
코딩 가이드라인에 따르면 복잡한 로직은 전용 함수나 HOC로 추상화해야 합니다. 특히 lines 38-45의 중첩된 조건 로직은 별도 헬퍼 함수로 분리하는 것을 권장합니다.
🔎 리팩토링 제안
// 외부 URL 처리를 별도 함수로 분리 const handleExternalForm = (externalUrl: string | undefined) => { const trimmedUrl = externalUrl?.trim(); if (trimmedUrl) { window.open(trimmedUrl, '_blank', 'noopener,noreferrer'); return true; } return false; }; const goWithForm = async (formId: string) => { try { const formDetail = await getApplication(clubId, formId); if (formDetail?.formMode === ApplicationFormMode.EXTERNAL) { if (handleExternalForm(formDetail.externalApplicationUrl)) { return; } } navigate(`/application/${clubId}/${formId}`, { state: { formDetail } }); setIsOpen(false); } catch (error) { console.error('지원서 조회 중 오류가 발생했습니다', error); alert('지원서 정보를 불러오는 중 오류가 발생했습니다. 다시 시도해주세요.'); } };Based on coding guidelines: "Abstract complex logic/interactions into dedicated components/HOCs" and "Avoid hidden side effects".
57-60: 비일관적인 비동기 처리 패턴을 통일하세요.Line 59에서
void goWithForm(option.id)를 사용하지만, line 78에서는await goWithForm(list[0].id)로 동일한 함수를 await하고 있습니다. 이러한 비일관성은 에러 처리와 실행 순서에 혼란을 줄 수 있습니다.코딩 가이드라인에 따르면 유사한 함수/훅에 대해 일관된 반환 타입과 사용 패턴을 유지해야 합니다.
🔎 일관성 있는 패턴 제안
const openByOption = (option?: ApplicationForm) => { if (!option) return; - void goWithForm(option.id); + // handleClick 내부의 await 패턴과 일관성 유지 + goWithForm(option.id).catch((error) => { + console.error('지원서 처리 중 오류:', error); + }); };Based on coding guidelines: "Use consistent return types for similar functions/hooks".
62-88: 복잡한 handleClick 함수를 더 작은 단위로 분리하세요.
handleClick함수가 너무 많은 책임을 가지고 있습니다:
- 이벤트 추적
- 마감 상태 검증
- 지원서 옵션 조회
- 단일/다중 옵션 분기 처리
- 에러 핸들링
- 모달 상태 관리
코딩 가이드라인에 따르면 복잡한 로직은 전용 컴포넌트나 커스텀 훅으로 분리해야 합니다. 특히 지원서 옵션 조회 및 처리 로직은 별도의 커스텀 훅(예:
useApplicationFlow)으로 추출하는 것을 권장합니다.🔎 커스텀 훅으로 분리하는 예시
// useApplicationFlow.ts const useApplicationFlow = (clubId: string) => { const navigate = useNavigate(); const handleSingleOption = async (formId: string) => { const formDetail = await getApplication(clubId, formId); // ... 폼 처리 로직 }; const fetchAndProcessOptions = async () => { const list = await getApplicationOptions(clubId); if (list.length === 0) { alert('현재 이용 가능한 지원서가 없습니다.'); return { shouldOpenModal: false, options: [] }; } if (list.length === 1) { await handleSingleOption(list[0].id); return { shouldOpenModal: false, options: [] }; } return { shouldOpenModal: true, options: list }; }; return { fetchAndProcessOptions, handleSingleOption }; }; // ClubApplyButton.tsx에서 사용 const handleClick = async () => { trackEvent(USER_EVENT.CLUB_APPLY_BUTTON_CLICKED); if (deadlineText === RECRUITMENT_STATUS.CLOSED) { alert(`현재 ${clubDetail.name} 동아리는 모집 기간이 아닙니다.`); return; } try { const { shouldOpenModal, options: newOptions } = await fetchAndProcessOptions(); if (shouldOpenModal) { setOptions(newOptions); setIsOpen(true); } } catch (e) { console.error('지원서 옵션 조회 중 오류가 발생했습니다.', e); alert('지원서 옵션을 불러오는 중 오류가 발생했습니다. 다시 시도해주세요.'); } };Based on coding guidelines: "Abstract complex logic/interactions into dedicated components/HOCs" and "Break down broad state management into smaller, focused hooks/contexts to reduce coupling".
90-106: 중첩된 조건문을 단순화하고 boolean 조건을 명확한 변수명으로 추출하세요.코딩 가이드라인에 따르면:
- 복잡한/중첩된 삼항 연산자는
if/else나 IIFE로 교체해야 합니다- 복잡한 boolean 조건은 명확한 의미를 가진 명명된 변수로 할당해야 합니다
Lines 98-103의 중첩된 조건 로직은 가독성을 저해합니다.
🔎 리팩토링 제안
const renderButtonContent = () => { if (deadlineText === RECRUITMENT_STATUS.CLOSED) { return RECRUITMENT_STATUS.CLOSED; } const shouldShowDeadline = deadlineText && deadlineText !== RECRUITMENT_STATUS.ALWAYS; return ( <> 지원하기 {shouldShowDeadline && ( <> <Styled.Separator /> {deadlineText} </> )} </> ); };Based on coding guidelines: "Replace complex/nested ternaries with if/else or IIFEs for readability" and "Assign complex boolean conditions to named variables for explicit meaning".
📜 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 (9)
frontend/src/pages/ClubDetailPage/ClubDetailPage.tsxfrontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.styles.tsfrontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsxfrontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsxfrontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.styles.tsfrontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsxfrontend/src/types/club.tsfrontend/src/utils/recruitmentDateParser.test.tsfrontend/src/utils/recruitmentDateParser.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/ClubApplyButton/ClubApplyButton.styles.tsfrontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.styles.tsfrontend/src/utils/recruitmentDateParser.tsfrontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsxfrontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsxfrontend/src/utils/recruitmentDateParser.test.tsfrontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsxfrontend/src/pages/ClubDetailPage/ClubDetailPage.tsxfrontend/src/types/club.ts
frontend/**/*.{ts,tsx}
📄 CodeRabbit inference engine (frontend/.cursorrules)
Use consistent return types for similar functions/hooks
Files:
frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.styles.tsfrontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.styles.tsfrontend/src/utils/recruitmentDateParser.tsfrontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsxfrontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsxfrontend/src/utils/recruitmentDateParser.test.tsfrontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsxfrontend/src/pages/ClubDetailPage/ClubDetailPage.tsxfrontend/src/types/club.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/ClubDetailFooter/ClubDetailFooter.tsxfrontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsxfrontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsxfrontend/src/pages/ClubDetailPage/ClubDetailPage.tsx
🧠 Learnings (4)
📚 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.styles.tsfrontend/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/ShareButton/ShareButton.styles.tsfrontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsxfrontend/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/ShareButton/ShareButton.tsxfrontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsxfrontend/src/pages/ClubDetailPage/ClubDetailPage.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} : Abstract complex logic/interactions into dedicated components/HOCs
Applied to files:
frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
🧬 Code graph analysis (4)
frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx (1)
frontend/src/utils/recruitmentDateParser.ts (1)
recruitmentDateParser(3-18)
frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx (2)
frontend/src/hooks/queries/club/useGetClubDetail.ts (1)
useGetClubDetail(6-20)frontend/src/constants/eventName.ts (1)
USER_EVENT(1-38)
frontend/src/utils/recruitmentDateParser.test.ts (1)
frontend/src/utils/recruitmentDateParser.ts (1)
recruitmentDateParser(3-18)
frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx (3)
frontend/src/hooks/queries/club/useGetClubDetail.ts (1)
useGetClubDetail(6-20)frontend/src/types/application.ts (1)
ApplicationForm(65-68)frontend/src/constants/eventName.ts (1)
USER_EVENT(1-38)
🔇 Additional comments (7)
frontend/src/utils/recruitmentDateParser.test.ts (1)
37-45: LGTM!빈 문자열과 '미정' 입력에 대한 null 반환 케이스가 적절히 추가되었습니다. 기존 에러 케이스들도 잘 유지되어 있어 함수의 모든 동작이 검증되고 있습니다.
frontend/src/utils/recruitmentDateParser.ts (1)
3-4: LGTM!'미정' 또는 빈 값에 대한 null 반환 처리가 적절합니다. 조기 반환 패턴을 사용하여 코드 가독성도 좋고, 반환 타입
Date | null로 타입 안전성도 확보되었습니다.frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.styles.ts (1)
1-12: LGTM!ShareButton 컴포넌트의 스타일 정의가 명확하고 간결합니다. 컨테이너와 아이콘을 분리하여 관리하는 구조가 적절합니다.
frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (1)
83-86: LGTM!ClubDetailFooter 컴포넌트가 적절한 위치에 추가되었고, 필요한 props도 올바르게 전달되고 있습니다. clubDetail의 null 체크도 이미 상위에서 수행되고 있어 안전합니다.
frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx (2)
11-13: LGTM!모듈 레벨에 상수를 정의하여 코드의 가독성과 유지보수성을 높였습니다.
22-25: LGTM!Kakao SDK 초기화 상태를 확인하는 방어 로직이 적절히 구현되어 있습니다.
frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.styles.ts (1)
12-39: 반응형 디자인이 잘 구현되어 있습니다.버튼 스타일이 명확하고, 500px 이하 화면에서 적절한 너비로 조정되는 반응형 디자인이 잘 적용되어 있습니다.
frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.styles.ts
Show resolved
Hide resolved
frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
Show resolved
Hide resolved
frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
Show resolved
Hide resolved
| const deadlineText = getDeadlineText( | ||
| recruitmentDateParser(recruitmentStart), | ||
| recruitmentDateParser(recruitmentEnd), | ||
| new Date(), | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
날짜 파싱 에러 처리를 추가하세요.
recruitmentDateParser는 잘못된 날짜 형식에 대해 에러를 throw하는데, 현재 에러 처리가 없어 컴포넌트가 크래시할 수 있습니다. 예를 들어, recruitmentStart나 recruitmentEnd가 빈 문자열이나 '미정'이 아니면서 잘못된 형식(예: "2025-05-25")일 경우 에러가 발생합니다.
사용자 경험 개선을 위해 try-catch로 에러를 처리하고 적절한 fallback UI를 표시하거나, 최소한 에러 로깅을 추가하는 것을 권장합니다.
🔎 에러 처리 추가 예시
const ClubDetailFooter = ({
recruitmentStart,
recruitmentEnd,
}: ClubDetailFooterProps) => {
+ let deadlineText: string;
+ try {
- const deadlineText = getDeadlineText(
- recruitmentDateParser(recruitmentStart),
- recruitmentDateParser(recruitmentEnd),
- new Date(),
- );
+ deadlineText = getDeadlineText(
+ recruitmentDateParser(recruitmentStart),
+ recruitmentDateParser(recruitmentEnd),
+ new Date(),
+ );
+ } catch (error) {
+ console.error('모집 기간 파싱 오류:', error);
+ deadlineText = '모집 기간 확인 불가';
+ }
return (
<Styled.ClubDetailFooterContainer>
<ClubApplyButton deadlineText={deadlineText} />
</Styled.ClubDetailFooterContainer>
);
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const deadlineText = getDeadlineText( | |
| recruitmentDateParser(recruitmentStart), | |
| recruitmentDateParser(recruitmentEnd), | |
| new Date(), | |
| ); | |
| const ClubDetailFooter = ({ | |
| recruitmentStart, | |
| recruitmentEnd, | |
| }: ClubDetailFooterProps) => { | |
| let deadlineText: string; | |
| try { | |
| deadlineText = getDeadlineText( | |
| recruitmentDateParser(recruitmentStart), | |
| recruitmentDateParser(recruitmentEnd), | |
| new Date(), | |
| ); | |
| } catch (error) { | |
| console.error('모집 기간 파싱 오류:', error); | |
| deadlineText = '모집 기간 확인 불가'; | |
| } | |
| return ( | |
| <Styled.ClubDetailFooterContainer> | |
| <ClubApplyButton deadlineText={deadlineText} /> | |
| </Styled.ClubDetailFooterContainer> | |
| ); | |
| }; |
🤖 Prompt for AI Agents
In
frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx
around lines 15-19, the calls to recruitmentDateParser can throw on invalid
input and currently crash the component; wrap the parsing and getDeadlineText
call in a try-catch, log the error (console.error or a logger) and provide a
safe fallback (e.g., nulls or a "날짜 정보 없음" deadlineText) so the component
renders without breaking; ensure getDeadlineText is only called with valid Date
objects and the fallback path renders appropriate UI or hides the deadline
section.
| window.Kakao.Share.sendDefault({ | ||
| objectType: 'feed', | ||
| content: { | ||
| title: clubDetail.name, | ||
| description: clubDetail.description, | ||
| imageUrl: clubDetail.logo ? clubDetail.logo : DEFAULT_IMAGE_URL, | ||
| link: { | ||
| mobileWebUrl: `${MOADONG_BASE_URL}${clubDetail.id}`, | ||
| webUrl: `${MOADONG_BASE_URL}${clubDetail.id}`, | ||
| }, | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
카카오 공유 description에 잘못된 타입이 전달되고 있습니다.
Line 31에서 clubDetail.description을 카카오 공유 description으로 사용하고 있지만, ClubDetail 타입의 description은 DetailedDescription 객체입니다(문자열이 아님). 이로 인해 카카오톡 공유 시 [object Object]가 표시되거나 런타임 에러가 발생할 수 있습니다.
clubDetail.introduction 또는 clubDetail.description.introDescription을 사용해야 합니다.
🔎 수정 제안
window.Kakao.Share.sendDefault({
objectType: 'feed',
content: {
title: clubDetail.name,
- description: clubDetail.description,
+ description: clubDetail.introduction,
imageUrl: clubDetail.logo ? clubDetail.logo : DEFAULT_IMAGE_URL,
link: {
mobileWebUrl: `${MOADONG_BASE_URL}${clubDetail.id}`,
webUrl: `${MOADONG_BASE_URL}${clubDetail.id}`,
},
},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| window.Kakao.Share.sendDefault({ | |
| objectType: 'feed', | |
| content: { | |
| title: clubDetail.name, | |
| description: clubDetail.description, | |
| imageUrl: clubDetail.logo ? clubDetail.logo : DEFAULT_IMAGE_URL, | |
| link: { | |
| mobileWebUrl: `${MOADONG_BASE_URL}${clubDetail.id}`, | |
| webUrl: `${MOADONG_BASE_URL}${clubDetail.id}`, | |
| }, | |
| }, | |
| window.Kakao.Share.sendDefault({ | |
| objectType: 'feed', | |
| content: { | |
| title: clubDetail.name, | |
| description: clubDetail.introduction, | |
| imageUrl: clubDetail.logo ? clubDetail.logo : DEFAULT_IMAGE_URL, | |
| link: { | |
| mobileWebUrl: `${MOADONG_BASE_URL}${clubDetail.id}`, | |
| webUrl: `${MOADONG_BASE_URL}${clubDetail.id}`, | |
| }, | |
| }, |
🤖 Prompt for AI Agents
In frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx
around lines 27 to 37, the Kakao share payload uses clubDetail.description which
is a DetailedDescription object, causing “[object Object]” or runtime errors;
replace it with a string field such as clubDetail.introduction or
clubDetail.description.introDescription (prefer introduction if available), and
ensure you coerce to a string with a safe fallback (e.g., empty string or
DEFAULT_DESCRIPTION) so the Kakao description always receives a plain string.
There was a problem hiding this 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 (1)
frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.styles.ts (1)
3-17: 매직 넘버를 상수로 추출하는 것을 고려해보세요.스타일 값들(
10px,40px,16px,#cdcdcd)이 하드코딩되어 있습니다. 디자인 시스템이나 테마에서 관리하거나 명명된 상수로 추출하면 유지보수성이 향상됩니다.또한
z-index: 1050은 상당히 높은 값인데, TODO 주석에서 이미 조정이 필요함을 인지하고 계신 것으로 보입니다. 모달 분리 작업 시 함께 개선하시면 좋을 것 같습니다.🔎 제안하는 리팩토링 예시
테마나 상수 파일에서 값을 관리하는 방식:
+const FOOTER_Z_INDEX = 1050; // TODO: Portal로 모달 분리 후 header보다 낮게 재조정 +const FOOTER_PADDING = '10px 40px'; +const FOOTER_GAP = '16px'; +const BORDER_COLOR = '#cdcdcd'; // 또는 theme.colors.gray300 + export const ClubDetailFooterContainer = styled.div` position: sticky; bottom: 0; width: 100%; - z-index: 1050; // TODO: Portal로 모달 분리 후 header보다 낮게 재조정 + z-index: ${FOOTER_Z_INDEX}; - padding: 10px 40px; + padding: ${FOOTER_PADDING}; display: flex; align-items: center; justify-content: space-between; - gap: 16px; + gap: ${FOOTER_GAP}; background-color: white; - border-top: 1px solid #cdcdcd; + border-top: 1px solid ${BORDER_COLOR}; `;
📜 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/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.styles.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/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.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
🧠 Learnings (1)
📓 Common learnings
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')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.
#️⃣연관된 이슈
#977
📝작업 내용
삭제되었던 하단 모집버튼을 복구하였습니다
중점적으로 리뷰받고 싶은 부분(선택)
논의하고 싶은 부분(선택)
🫡 참고사항
Summary by CodeRabbit
릴리스 노트
새로운 기능
테스트
✏️ Tip: You can customize this high-level summary in your review settings.