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.
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.
날짜 파싱 에러 처리를 추가하세요.
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.
카카오 공유 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.
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.