Skip to content

Comments

[feature]상세페이지에 동아리 추천 추가#665

Merged
Zepelown merged 3 commits intodevelop-fefrom
feature/#657-recommend-club-fe-MOA-162
Aug 17, 2025
Merged

[feature]상세페이지에 동아리 추천 추가#665
Zepelown merged 3 commits intodevelop-fefrom
feature/#657-recommend-club-fe-MOA-162

Conversation

@Zepelown
Copy link
Member

@Zepelown Zepelown commented Aug 17, 2025

#️⃣연관된 이슈

#657
#655

📝작업 내용

image
  • 동아리 상세 페이지에 동아리 추천 섹션을 추가했습니다.
  • 6개 중 4개는 같은 분과(카테고리)이고 나머지는 전체 랜덤으로 구성했습니다.
  • 믹스패널 A/B테스트를 위한 더미 코드가 있습니다
    (다른 유저 그룹은 Swap할 수 있는 구조로 만들기 위한 Swiper 라이브러리를 추가했습니다)
  • 버그로 인해 Swiper 개발이 늦어지는 관계로 대체제로 GridList로 올립니다.

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

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

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

🫡 참고사항

Summary by CodeRabbit

  • 새로운 기능
    • 클럽 상세 페이지에 “추천 동아리” 섹션이 추가되었습니다. 최대 6개 추천 카드를 그리드 형태로 표시하며 반응형 레이아웃과 클릭 시 해당 동아리로 이동하는 동작을 지원합니다. 카드에 로고(기본 이미지 포함), 상태, 이름, 소개 및 태그가 표시됩니다.
  • Chores
    • 슬라이더 라이브러리(swiper)를 의존성에 추가하고 개발 빌드에서 해당 CSS가 처리되도록 설정을 업데이트했습니다.

@Zepelown Zepelown added 📬 API 서버 API 통신 작업 💻 FE Frontend labels Aug 17, 2025
@vercel
Copy link

vercel bot commented Aug 17, 2025

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

Project Deployment Preview Comments Updated (UTC)
moadong Ready Ready Preview Comment Aug 17, 2025 8:35am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 17, 2025

Warning

.coderabbit.yaml has a parsing error

The CodeRabbit configuration file in this repository has a parsing error and default settings were used instead. Please fix the error(s) in the configuration file. You can initialize chat with CodeRabbit to get help with the configuration file.

💥 Parsing errors (1)
Validation error: Invalid regex pattern for base branch. Received: "**" at "reviews.auto_review.base_branches[0]"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

클럽 상세 페이지에 추천 동아리 UI를 추가했다. RecommendedClubs 및 ClubCard 컴포넌트를 신설하고 ClubDetailPage에 렌더링을 연결했으며, 타입에 recommendClubs?: Club[] 필드를 추가했다. 또한 swiper 패키지를 의존성에 추가하고 CSS 로더 include에 swiper 경로를 포함했다.

Changes

Cohort / File(s) Summary
빌드/패키지 업데이트
frontend/config/webpack.dev.ts, frontend/package.json
swiper 디렉터리를 CSS 로더 include에 추가하고 swiper@^11.2.10 의존성 추가.
클럽 상세 페이지 통합
frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx
RecommendedClubs 컴포넌트 임포트 및 clubDetail.recommendClubs ?? [] 전달하여 렌더링 추가.
추천 카드 컴포넌트 및 스타일
frontend/src/pages/ClubDetailPage/components/ClubCard/ClubCard.tsx, .../ClubCard/ClubCard.styles.ts
추천 동아리 카드 컴포넌트 추가(로고, 이름, 상태, 태그, 클릭 시 Mixpanel 트래킹 및 150ms 지연 후 네비게이션). 스타일은 styled-components로 정의.
추천 섹션 컴포넌트 및 스타일
frontend/src/pages/ClubDetailPage/components/RecommendedClubs/RecommendedClubs.tsx, .../RecommendedClubs/RecommendedClubs.styles.tsx
최대 6개의 추천 동아리를 그리드 형태로 렌더링하는 RecommendedClubs 컴포넌트 추가(빈 목록이면 null 반환). swiper/css를 임포트하나 슬라이더 로직은 없음.
타입 및 이벤트 상수
frontend/src/types/club.ts, frontend/src/constants/eventName.ts
ClubDetailrecommendClubs?: Club[] 필드 추가. 이벤트 상수에 RECOMMENDED_CLUB_CLICKED 추가.

Sequence Diagram(s)

sequenceDiagram
  participant User as 사용자
  participant Page as ClubDetailPage
  participant RC as RecommendedClubs
  participant UI as UI(Grid)
  User->>Page: 페이지 진입
  Page->>RC: clubs=clubDetail.recommendClubs 전달
  RC->>UI: 최대 6개 그리드로 렌더
Loading
sequenceDiagram
  participant User as 사용자
  participant Card as ClubCard
  participant Analytics as Mixpanel
  participant Router as Router
  User->>Card: 카드 클릭
  Card->>Analytics: EVENT_NAME.RECOMMENDED_CLUB_CLICKED 전송
  Card->>Router: 150ms 후 /club/{id}로 네비게이션
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
동아리 상세 페이지에 추천 동아리를 보여준다 (MOA-162)

Assessment against linked issues: Out-of-scope changes

(해당 없음)

Possibly related issues

Possibly related PRs

Suggested labels

✨ Feature

Suggested reviewers

  • seongwon030
  • oesnuj

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/#657-recommend-club-fe-MOA-162

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

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

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

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

리뷰 한번 봐주시면 감사하겠습니다

recruitmentTarget: string;
socialLinks: Record<SNSPlatform, string>;
externalApplicationUrl?: string;
recommendClubs?: Club
Copy link
Contributor

Choose a reason for hiding this comment

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

여기 왜 Club[]이 아니고 Club일까요? 타입캐스팅하는거보면 응답은 Club[]로 들어올꺼같은데

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

🧹 Nitpick comments (12)
frontend/package.json (1)

40-41: 새 의존성(swiper) 추가 적절. React 19/번들 호환성 확인 권장.

Swiper 11.x의 React 19 peer 호환 및 번들 시 CSS 포함이 설치/빌드 환경에서 경고 없이 동작하는지 확인해 주세요. 또한 A/B용 슬라이더가 조건부로만 사용된다면 동적 import로 번들 크기 영향 최소화도 고려할 만합니다.

검증 항목:

  • npm/yarn 설치 시 peerDependencies 경고 유무
  • prod 빌드에서 Swiper CSS가 정상 포함(플래시 없는지)되는지
  • 슬라이더 레이아웃을 동적 import로 분리해 초기 번들 축소 가능 여부
frontend/config/webpack.dev.ts (1)

58-61: dev 환경에서 Swiper CSS 포함 경로 추가 OK. prod 설정도 동기화 필요.

개발 빌드에선 문제 없어 보입니다. 다만 프로덕션 webpack 설정에서도 동일 경로를 include하지 않으면 Swiper 스타일이 누락될 수 있습니다. 공통 include 목록을 상수로 분리해 dev/prod에서 공유하는 방식도 고려해 주세요.

검증/개선 제안:

  • webpack.prod.ts에 동일 include 경로 추가 확인
  • config/shared.ts 등으로 CSS include 경로 상수화 후 dev/prod에서 공유
frontend/src/pages/ClubDetailPage/components/RecommendedClubs/RecommendedClubs.styles.tsx (1)

3-12: 디자인 토큰/테마 사용 고려.

고정값(여백, 색상, radius)을 theme 변수로 치환하면 유지보수성과 일관성이 좋아집니다. 예: border: 1px solid ${p => p.theme.colors.borderNormal};, gap: ${p => p.theme.spacing.lg};, border-radius: ${p => p.theme.radius.lg};

frontend/src/pages/ClubDetailPage/components/ClubCard/ClubCard.styles.ts (3)

9-15: 중복 border-radius 선언 (14px vs 18px).

중복 선언은 혼란을 초래합니다. 최종 의도값 한 곳으로 통일하세요. 아래는 18px로 통일하는 예시입니다.

-  border-radius: 14px;
   padding: 20px;
   background-color: #fff;
   width: 100%;
   height: 170px;
-  border-radius: 18px;
+  border-radius: 18px;

59-65: 긴 소개 문구 잘림 처리 필요.

white-space: nowrap;만 있으면 오버플로우가 일어납니다. 시각적 품질을 위해 ellipsis 처리 또는 멀티라인 클램핑을 적용하세요.

예시(단일 라인 ellipsis):

   line-height: 16px;
-  white-space: nowrap;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;

예시(멀티라인 2줄 클램프):

display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;

3-6: $state prop가 스타일에서 사용되지 않습니다.

미사용 prop은 타입 복잡도와 런타임 prop 전달만 증가시킵니다. 실제 스타일 분기 계획이 없다면 제거하거나, 상태별 시각 분기를 적용하세요.

제거 예시:

-const CardContainer = styled.div<{
-  $state: string;
-  $isClicked: boolean;
-}>`
+const CardContainer = styled.div<{ $isClicked: boolean }>`
frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (1)

73-74: 타입 단언(as Club[]) 제거로 타입 안정성 확보

(clubDetail.recommendClubs ?? []) as Club[] 대신, ClubDetail 타입에 recommendClubs?: Club[]를 반영하고 단언을 제거하세요. 런타임에는 동일하게 동작하면서 컴파일 타임 타입 안정성이 개선됩니다.

다음 변경을 권장합니다.

-        <RecommendedClubs clubs={(clubDetail.recommendClubs ?? []) as Club[]} />
+        <RecommendedClubs clubs={clubDetail.recommendClubs ?? []} />

ClubDetail 타입에 recommendClubs?: Club[]가 정의되어 있지 않다면 추가가 필요합니다(상단 코멘트의 검증 스크립트 참고).

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

15-27: 매직 넘버(150ms) 상수화 및 타임아웃 정리(cleanup) 고려

클릭 지연 시간 150ms는 의미가 드러나지 않습니다. 상수로 분리하면 의도가 명확해지고 재사용/조정이 수월합니다. 또한 언마운트 타이밍에 타임아웃을 정리하면 드물게 발생할 수 있는 setState on unmounted 컴포넌트 경고를 예방할 수 있습니다.

다음과 같이 수정 제안합니다.

-    setTimeout(() => {
+    setTimeout(() => {
       setIsClicked(false);
       navigate(`/club/${club.id}`);
-    }, 150);
+    }, CLICK_DELAY_MS);

아래와 같이 파일 상단에 상수를 추가하고(선택) cleanup을 더할 수 있습니다.

// 파일 상단 인근에 추가
const CLICK_DELAY_MS = 150;

// 선택: cleanup을 원한다면
import { useEffect, useRef } from 'react';

const timerRef = useRef<number | null>(null);

const handleNavigate = () => {
  setIsClicked(true);
  mixpanel.track('Recommended ClubCard Clicked', {
    club_id: club.id,
    club_name: club.name,
    recruitment_status: club.recruitmentStatus,
  });
  timerRef.current = window.setTimeout(() => {
    setIsClicked(false);
    navigate(`/club/${club.id}`);
  }, CLICK_DELAY_MS);
};

useEffect(() => {
  return () => {
    if (timerRef.current) {
      clearTimeout(timerRef.current);
    }
  };
}, []);

29-33: 키보드 접근성 보강 제안(role/tabIndex/onKeyDown)

카드 전체를 클릭 가능하게 만들었으므로 키보드 사용자도 접근할 수 있도록 role, tabIndex, onKeyDown을 추가하는 것을 권장합니다.

다음과 같이 최소 변경을 제안합니다.

-    <Styled.CardContainer
+    <Styled.CardContainer
       $state={club.recruitmentStatus}
       $isClicked={isClicked}
-      onClick={handleNavigate}>
+      role="button"
+      tabIndex={0}
+      aria-label={`${club.name} 상세 보기`}
+      onClick={handleNavigate}
+      onKeyDown={handleKeyDown}>

핸들러는 다음과 같이 추가할 수 있습니다.

const handleKeyDown = (e: React.KeyboardEvent) => {
  if (e.key === 'Enter' || e.key === ' ') {
    e.preventDefault();
    handleNavigate();
  }
};
frontend/src/pages/ClubDetailPage/components/RecommendedClubs/RecommendedClubs.tsx (3)

30-30: 매직 넘버(6) 상수화로 의도 명확화

추천 최대 개수 6은 요구사항 상수입니다. 상수로 분리하면 변경 용이성과 가독성이 향상됩니다.

-  const displayClubs = clubs.slice(0, 6);
+  const displayClubs = clubs.slice(0, MAX_RECOMMEND_CLUBS);

파일 상단 인근에 다음을 추가하세요.

const MAX_RECOMMEND_CLUBS = 6;

51-51: 슬라이더 설정값 상수화(2, 16) 및 불필요한 boolean 리터럴 제거

slidesPerView={2}, spaceBetween={16}는 상수로 분리하면 의도가 드러납니다. 또한 freeMode={true} 생략 가능합니다.

-        <Swiper spaceBetween={16} slidesPerView={2} freeMode={true}>
+        <Swiper
+          spaceBetween={SLIDE_SPACE_PX}
+          slidesPerView={SLIDES_PER_VIEW}
+          freeMode
+        >

상단에 상수 추가:

const SLIDES_PER_VIEW = 2;
const SLIDE_SPACE_PX = 16;

47-60: 옵션: B 그룹에서만 Swiper 동적 로딩으로 번들 최적화

AB에서 B 그룹 사용자만 슬라이더를 사용하므로, React.lazy + Suspense로 Swiper를 지연 로딩하면 초기 번들 크기를 줄일 수 있습니다. CSS는 전역 주입이 필요하면 현행 유지, 코드만 동적 import하는 전략을 고려해보세요.

원한다면 예시 구현안을 제안드릴 수 있습니다.

📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear 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 97aedd5 and eeb7e05.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (8)
  • frontend/config/webpack.dev.ts (1 hunks)
  • frontend/package.json (1 hunks)
  • frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (2 hunks)
  • frontend/src/pages/ClubDetailPage/components/ClubCard/ClubCard.styles.ts (1 hunks)
  • frontend/src/pages/ClubDetailPage/components/ClubCard/ClubCard.tsx (1 hunks)
  • frontend/src/pages/ClubDetailPage/components/RecommendedClubs/RecommendedClubs.styles.tsx (1 hunks)
  • frontend/src/pages/ClubDetailPage/components/RecommendedClubs/RecommendedClubs.tsx (1 hunks)
  • frontend/src/types/club.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
frontend/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (frontend/.cursorrules)

frontend/**/*.{ts,tsx}: Replace magic numbers with named constants for clarity.
Replace complex or nested ternary operators with if/else statements or IIFEs for readability.
Assign complex boolean conditions to named variables.
Use consistent return types for similar functions and hooks.
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle).
Use unique, descriptive names for custom wrappers and functions to avoid ambiguity.
Define constants near related logic or ensure names link them clearly.

Files:

  • frontend/src/types/club.ts
  • frontend/src/pages/ClubDetailPage/components/RecommendedClubs/RecommendedClubs.styles.tsx
  • frontend/src/pages/ClubDetailPage/components/ClubCard/ClubCard.styles.ts
  • frontend/config/webpack.dev.ts
  • frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx
  • frontend/src/pages/ClubDetailPage/components/ClubCard/ClubCard.tsx
  • frontend/src/pages/ClubDetailPage/components/RecommendedClubs/RecommendedClubs.tsx
frontend/**/*.tsx

📄 CodeRabbit Inference Engine (frontend/.cursorrules)

frontend/**/*.tsx: Abstract complex logic/interactions into dedicated components or higher-order 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.
Break down broad state management into smaller, focused hooks or contexts.
Use component composition instead of props drilling.

Files:

  • frontend/src/pages/ClubDetailPage/components/RecommendedClubs/RecommendedClubs.styles.tsx
  • frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx
  • frontend/src/pages/ClubDetailPage/components/ClubCard/ClubCard.tsx
  • frontend/src/pages/ClubDetailPage/components/RecommendedClubs/RecommendedClubs.tsx
🧠 Learnings (2)
📓 Common learnings
Learnt from: seongwon030
PR: Moadong/moadong#195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.
📚 Learning: 2025-03-19T05:18:07.818Z
Learnt from: seongwon030
PR: Moadong/moadong#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/ClubCard/ClubCard.tsx
  • frontend/src/pages/ClubDetailPage/components/RecommendedClubs/RecommendedClubs.tsx
🧬 Code Graph Analysis (3)
frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (1)
frontend/src/types/club.ts (1)
  • Club (3-12)
frontend/src/pages/ClubDetailPage/components/ClubCard/ClubCard.tsx (2)
frontend/src/types/club.ts (1)
  • Club (3-12)
frontend/src/pages/MainPage/components/ClubCard/ClubCard.tsx (1)
  • club (11-55)
frontend/src/pages/ClubDetailPage/components/RecommendedClubs/RecommendedClubs.tsx (1)
frontend/src/types/club.ts (1)
  • Club (3-12)
🔇 Additional comments (3)
frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (1)

73-73: 섹션 배치 적절합니다

사진 리스트 바로 아래에 추천 섹션을 배치한 점, 페이지 흐름상 자연스럽고 PR 목적과 부합합니다.

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

36-41: 로고 폴백 및 상태 박스 구성 적절

로고 폴백과 모집 상태 표시는 사용자 인지에 도움이 됩니다. 구현 괜찮습니다.

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

28-45: 빈 배열 early-return 처리 적절

데이터 미존재 시 null 반환으로 불필요한 마크업을 피하는 방식이 적절합니다. Grid 렌더링 로직도 간결합니다.

Comment on lines +17 to +18
import RecommendedClubs from '@/pages/ClubDetailPage/components/RecommendedClubs/RecommendedClubs';
import { Club } from '@/types/club';
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

불필요한 타입 단언 제거 및 import 정리 제안

Club 타입 import는 아래에서 (…) as Club[] 단언을 위해서만 사용되고 있습니다. 실제 데이터 모델(ClubDetail)의 recommendClubsClub[] | undefined로 올바르게 타입 선언하면 단언이 필요 없습니다.
[suggest_optional_refactor]
다음 변경을 권장합니다.

  • 이 파일: Club import 제거
-import { Club } from '@/types/club';
  • 아래 렌더링부(라인 73)의 단언 제거(별도 코멘트 참조)

추가로, ClubDetail 타입 정의에서 recommendClubs?: Club[]로 반영 부탁드립니다. 확인 스크립트를 첨부합니다.

다음 스크립트로 recommendClubs 타입 정의 상태를 확인할 수 있습니다.


🏁 Script executed:

#!/bin/bash
# recommendClubs 필드 및 ClubDetail 타입 정의/사용 위치 검색
rg -n -C3 -g "frontend/**" -P '\brecommendClubs\b'
echo "----"
rg -n -C3 -g "frontend/**" -P 'interface\s+ClubDetail\b|type\s+ClubDetail\b'

Length of output: 1508


불필요한 타입 단언 제거 및 recommendClubs 타입 정의 수정 필요

  • ClubDetailPage.tsx
    • 17–18행: import { Club } from '@/types/club'; 제거
    • 73행:
      - <RecommendedClubs clubs={(clubDetail.recommendClubs ?? []) as Club[]} />
      + <RecommendedClubs clubs={clubDetail.recommendClubs ?? []} />
  • 타입 정의 (frontend/src/types/club.ts)
    -  recommendClubs?: Club
    +  recommendClubs?: Club[]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import RecommendedClubs from '@/pages/ClubDetailPage/components/RecommendedClubs/RecommendedClubs';
import { Club } from '@/types/club';
// File: frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx
// Remove the unused Club import.
import RecommendedClubs from '@/pages/ClubDetailPage/components/RecommendedClubs/RecommendedClubs';
// import { Club } from '@/types/club';
// In the JSX where RecommendedClubs is rendered, drop the unnecessary assertion.
<RecommendedClubs clubs={clubDetail.recommendClubs ?? []} />
Suggested change
import RecommendedClubs from '@/pages/ClubDetailPage/components/RecommendedClubs/RecommendedClubs';
import { Club } from '@/types/club';
// File: frontend/src/types/club.ts
export interface Club {
// …other fields…
recommendClubs?: Club[]
}

Comment on lines +21 to +30
export const GridList = styled.div`
display: grid;
grid-template-columns: repeat(2, 1fr); // 기본 2열
gap: 16px;

// 화면 넓이 600px 이하일 경우 1열로 변경
@media (max-width: 600px) {
grid-template-columns: 1fr;
}
`;
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

styled-components 템플릿 내 // 주석은 유효하지 않습니다. CSS 파싱 오류 가능.

템플릿 리터럴 내부는 CSS이므로 //가 아닌 /* ... */를 사용해야 합니다. 현재 상태에선 해당 규칙들이 무시되거나 빌드 시 경고가 발생할 수 있습니다.

아래처럼 수정해 주세요.

 export const GridList = styled.div`
   display: grid;
-  grid-template-columns: repeat(2, 1fr); // 기본 2열
+  grid-template-columns: repeat(2, 1fr); /* 기본 2열 */
   gap: 16px;

-  // 화면 넓이 600px 이하일 경우 1열로 변경
+  /* 화면 넓이 600px 이하일 경우 1열로 변경 */
   @media (max-width: 600px) {
     grid-template-columns: 1fr;
   }
 `;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const GridList = styled.div`
display: grid;
grid-template-columns: repeat(2, 1fr); // 기본 2열
gap: 16px;
// 화면 넓이 600px 이하일 경우 1열로 변경
@media (max-width: 600px) {
grid-template-columns: 1fr;
}
`;
export const GridList = styled.div`
display: grid;
grid-template-columns: repeat(2, 1fr); /* 기본 2열 */
gap: 16px;
/* 화면 넓이 600px 이하일 경우 1열로 변경 */
@media (max-width: 600px) {
grid-template-columns: 1fr;
}
`;
🤖 Prompt for AI Agents
In
frontend/src/pages/ClubDetailPage/components/RecommendedClubs/RecommendedClubs.styles.tsx
around lines 21 to 30, the styled-components template literal contains
JavaScript-style `//` comments which are invalid in CSS and can cause
parsing/build errors; replace those `//` comments inside the template literal
with CSS comments using `/* ... */` (or remove them) so the CSS block becomes
valid, and scan the file for any other `//` inside template literals and convert
them as well.

Comment on lines 12 to 18
const getABGroup = (): 'A' | 'B' => {
const savedGroup = localStorage.getItem('recommendABGroup');
if (savedGroup === 'A' || savedGroup === 'B') return savedGroup;
const group = Math.random() < 0.5 ? 'A' : 'B';
localStorage.setItem('recommendABGroup', group);
return 'A';
};
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

A/B 그룹 로직 버그: 최초 진입 시 항상 'A' 반환

최초 실행 경로에서 localStorage에 무작위 그룹을 저장하고도 반환값을 'A'로 고정하고 있어 첫 노출이 항상 A가 됩니다. 저장한 group을 반환해야 합니다. 또한 localStorage 접근 실패에 대비한 try/catch도 권장합니다.

다음과 같이 수정해주세요.

-const getABGroup = (): 'A' | 'B' => {
-  const savedGroup = localStorage.getItem('recommendABGroup');
-  if (savedGroup === 'A' || savedGroup === 'B') return savedGroup;
-  const group = Math.random() < 0.5 ? 'A' : 'B';
-  localStorage.setItem('recommendABGroup', group);
-  return 'A';
-};
+const getABGroup = (): 'A' | 'B' => {
+  try {
+    const savedGroup = localStorage.getItem('recommendABGroup');
+    if (savedGroup === 'A' || savedGroup === 'B') return savedGroup;
+    const group = Math.random() < 0.5 ? 'A' : 'B';
+    localStorage.setItem('recommendABGroup', group);
+    return group;
+  } catch {
+    // 스토리지 접근 불가 시 안전 기본값
+    return 'A';
+  }
+};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const getABGroup = (): 'A' | 'B' => {
const savedGroup = localStorage.getItem('recommendABGroup');
if (savedGroup === 'A' || savedGroup === 'B') return savedGroup;
const group = Math.random() < 0.5 ? 'A' : 'B';
localStorage.setItem('recommendABGroup', group);
return 'A';
};
const getABGroup = (): 'A' | 'B' => {
try {
const savedGroup = localStorage.getItem('recommendABGroup');
if (savedGroup === 'A' || savedGroup === 'B') return savedGroup;
const group = Math.random() < 0.5 ? 'A' : 'B';
localStorage.setItem('recommendABGroup', group);
return group;
} catch {
// 스토리지 접근 불가 시 안전 기본값
return 'A';
}
};
🤖 Prompt for AI Agents
In
frontend/src/pages/ClubDetailPage/components/RecommendedClubs/RecommendedClubs.tsx
around lines 12 to 18, the A/B group logic stores a random group to localStorage
but always returns the literal 'A', causing first impressions to always be A;
change the function to return the actual chosen group and wrap
localStorage.getItem/setItem calls in try/catch to handle potential errors (on
read, fall back to random choice; on write, swallow/log the error and still
return the chosen group).

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 18
transition:
transform 0.2s ease-in-out,
Copy link
Member

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 21
mixpanel.track('Recommended ClubCard Clicked', {
club_id: club.id,
club_name: club.name,
recruitment_status: club.recruitmentStatus,
});
Copy link
Member

Choose a reason for hiding this comment

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

이 부분도

const trackEvent = useMixpanelTrack();

으로 변경해주심 될 것 같습니다

Copy link
Member

Choose a reason for hiding this comment

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

추가로 club.id는 추적이 필요할까요?

Copy link
Member Author

Choose a reason for hiding this comment

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

이전에 있는 걸 그대로 가져온 거긴한데 이름이 중복될 수도 있으니까 id도 같이 보관하면 좋을 거 같습니다.

clubs: Club[];
}

const getABGroup = (): 'A' | 'B' => {
Copy link
Member

Choose a reason for hiding this comment

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

이 부분도 당장 필요하지 않아서 제거해도 될 것 같아요

return 'A';
};

const RecommendedClubs = ({ clubs }: RecommendedClubsProps) => {
Copy link
Member

Choose a reason for hiding this comment

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

요기 전체가 AB테스트를 위한 로직이라면 다시 변경해야될 듯 합니다

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 (1)
frontend/src/types/club.ts (1)

27-27: 세미콜론 누락 (코드 스타일 일관성)

파일 전반의 필드 선언과 동일하게 세미콜론을 추가하는 것이 좋습니다.

-  recommendClubs?: Club[]
+  recommendClubs?: Club[];
📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear 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 eeb7e05 and c0dcdf1.

📒 Files selected for processing (1)
  • frontend/src/types/club.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
frontend/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (frontend/.cursorrules)

frontend/**/*.{ts,tsx}: Replace magic numbers with named constants for clarity.
Replace complex or nested ternary operators with if/else statements or IIFEs for readability.
Assign complex boolean conditions to named variables.
Use consistent return types for similar functions and hooks.
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle).
Use unique, descriptive names for custom wrappers and functions to avoid ambiguity.
Define constants near related logic or ensure names link them clearly.

Files:

  • frontend/src/types/club.ts
🧠 Learnings (2)
📓 Common learnings
Learnt from: seongwon030
PR: Moadong/moadong#195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.
📚 Learning: 2025-07-19T05:05:10.196Z
Learnt from: seongwon030
PR: Moadong/moadong#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/types/club.ts
🔇 Additional comments (1)
frontend/src/types/club.ts (1)

27-27: 타입 변경 LGTM — 추천 클럽은 배열(Club[])이어야 합니다

ClubDetailPage/RecommendedClubs 사용처와 일치합니다. 이제 사용처에서 (clubDetail.recommendClubs ?? []) as Club[]와 같은 불필요한 캐스팅은 제거 가능합니다.

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/pages/ClubDetailPage/ClubDetailPage.tsx (1)

17-18: 불필요한 Club 타입 import 제거

현재 파일 내에서 Club 타입을 직접 사용하지 않습니다. 이전 코멘트와 동일 제안입니다.

- import { Club } from '@/types/club';
🧹 Nitpick comments (1)
frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (1)

73-75: 빈 추천 목록일 때는 섹션 자체를 렌더하지 않기

빈 배열을 넘기며 항상 섹션을 렌더링하면 여백/타이틀만 남을 수 있습니다. 목록이 있을 때만 표시하도록 조건부 렌더링을 권장합니다.

-        <RecommendedClubs
-        clubs={clubDetail.recommendClubs ?? []} />
+        {(clubDetail.recommendClubs?.length ?? 0) > 0 && (
+          <RecommendedClubs clubs={clubDetail.recommendClubs!} />
+        )}
📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear 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 c0dcdf1 and 8dd4908.

📒 Files selected for processing (5)
  • frontend/src/constants/eventName.ts (1 hunks)
  • frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (2 hunks)
  • frontend/src/pages/ClubDetailPage/components/ClubCard/ClubCard.styles.ts (1 hunks)
  • frontend/src/pages/ClubDetailPage/components/ClubCard/ClubCard.tsx (1 hunks)
  • frontend/src/pages/ClubDetailPage/components/RecommendedClubs/RecommendedClubs.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/src/pages/ClubDetailPage/components/ClubCard/ClubCard.tsx
  • frontend/src/pages/ClubDetailPage/components/RecommendedClubs/RecommendedClubs.tsx
  • frontend/src/pages/ClubDetailPage/components/ClubCard/ClubCard.styles.ts
🧰 Additional context used
📓 Path-based instructions (2)
frontend/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (frontend/.cursorrules)

frontend/**/*.{ts,tsx}: Replace magic numbers with named constants for clarity.
Replace complex or nested ternary operators with if/else statements or IIFEs for readability.
Assign complex boolean conditions to named variables.
Use consistent return types for similar functions and hooks.
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle).
Use unique, descriptive names for custom wrappers and functions to avoid ambiguity.
Define constants near related logic or ensure names link them clearly.

Files:

  • frontend/src/constants/eventName.ts
  • frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx
frontend/**/*.tsx

📄 CodeRabbit Inference Engine (frontend/.cursorrules)

frontend/**/*.tsx: Abstract complex logic/interactions into dedicated components or higher-order 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.
Break down broad state management into smaller, focused hooks or contexts.
Use component composition instead of props drilling.

Files:

  • frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx
🧠 Learnings (1)
📓 Common learnings
Learnt from: seongwon030
PR: Moadong/moadong#195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.
🔇 Additional comments (1)
frontend/src/constants/eventName.ts (1)

8-8: 신규 이벤트 상수 추가 — 사용처 및 하드코딩 검증 완료

  • EVENT_NAME.RECOMMENDED_CLUB_CLICKEDClubCard 컴포넌트에서 정상적으로 호출됨(trackEvent)
  • 코드베이스 내에 문자열 리터럴 'Recommended Club Clicked' 직접 사용 사례 없음
  • 명칭도 기존 패턴(… Card Clicked)과 일치합니다

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📬 API 서버 API 통신 작업 💻 FE Frontend

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants