Skip to content

feat: SP1 매칭 현황 api 수정 및 카드, 토스트 수정#332

Merged
Dubabbi merged 20 commits intodevelopfrom
feat/#316/match-status
Sep 2, 2025
Merged

feat: SP1 매칭 현황 api 수정 및 카드, 토스트 수정#332
Dubabbi merged 20 commits intodevelopfrom
feat/#316/match-status

Conversation

@Dubabbi
Copy link
Contributor

@Dubabbi Dubabbi commented Aug 31, 2025

#️⃣ Related Issue

Closes #316

☀️ New-insight

새로운 디자인에서 토스트를 아이콘 없이 사용하는 게 필요해서 토스트 관련 코드를 수정했습니다.
싱글 카드에서 직관 스타일 칩이 안 나타나는 문제와 일대일 매칭 응답이 보이지 않는 오류를 해결했습니다.
또 프로필 이미지를 api 응답에서 가져오는 것으로 수정했습니다!

💎 PR Point

  • API를 v2로 전환하고 응답 스키마에 isCreated 필드를 반영했습니다.
  • 대기 상태 클릭 시 토스트 노출 로직을 추가했습니다.
  • 카드 헤더에서 1:1/그룹 타입별로 왕관 아이콘을 적절한 크기/위치로 표시합니다.
  • 매치 탭 스타일(하단 언더라인·간격 등)만 수정하여 디자인 요구사항을 맞췄습니다.
  • 토스트는 디자인에 맞게 아이콘 없는 버전도 선택적으로 사용 가능하도록 보강했습니다.

API / 타입

  • v2 엔드포인트로 변경.
  • 카드 데이터에 isCreated 필드 반영

UI/UX

  • 대기 상태 클릭 토스트 (MatchTabPanel)

    • 요청 대기 중 (1:1/그룹 공통) → “메이트의 요청을 기다리는 중입니다.”
    • 승인 대기 중 (1:1) → “메이트의 승인을 기다리는 중입니다.”
    • 승인 대기 중 (그룹) → “메이트 전원의 승인을 기다리는 중입니다.”
    • 토스트 노출 시 네비게이션/뮤테이션 방지(조기 return)
    • onClick는 항상 바인딩, 커서만 상태에 따라 변경(cursor-pointer)
  • 카드 헤더 왕관 표시 (CardHeader)

    • 색상 토큰 text-owner, z-index --z-card-ownertheme.css에 추가하여 사용
  • 매치 탭 스타일

    • match 타입 스타일 수정

📸 Screenshot

default.mp4

직관 스타일 칩 수정한 버전

image

Summary by CodeRabbit

  • New Features

    • 요청/승인 대기 카드 클릭 시 안내 토스트를 표시하고 이동을 중단합니다.
    • 내가 만든 카드에 소유자 표식(왕관) 표시 및 프로필 슬롯/이미지 개선.
    • 승인 완료 카드 클릭 시 사전 업데이트 후 결과 페이지로 이동합니다.
  • Style

    • 매칭 탭 인디케이터를 after 기반으로 변경하고 폭·간격·텍스트 조정.
    • 카드 프로필 테두리 제거, 테마에 소유자 색상·Z-Index 추가.
  • Bug Fixes / Accessibility

    • 비활성 카드에 aria-disabled 적용, 클릭/커서 동작 및 오류 토스트 옵션 개선.
  • Chores

    • 일부 상태 API 경로를 v2로 이동하고 테스트용 목데이터·미사용 훅 제거.

@Dubabbi Dubabbi self-assigned this Aug 31, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 31, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

카드 클릭 시 대기 상태 토스트를 우선 검사해 표시하면 네비게이션을 중단하고, 승인 완료 시 patch 호출 후 결과 페이지로 이동합니다. 카드 생성자(isCreated)로 왕관 오버레이를 추가하고, 이미지 슬롯화·칩 정규화·탭 스타일과 토스트 API·여러 mock 파일 및 useMate 훅이 제거되었습니다.

Changes

Cohort / File(s) Summary
Pending-toast gating & click flow
src/pages/match/components/match-tab-pannel.tsx, src/pages/match/utils/match-status.ts, src/shared/utils/show-error-toast.tsx
카드 클릭 시 getPendingToast(status,type)로 대기 메시지 확인 후 존재하면 showErrorToast(...) 표시하고 네비게이션 중단. onClick 단순화, aria-disabled 추가, isClickable 외부화, patchStageMutation.mutateAsync 호출 흐름 추가. showErrorToast 옵션형 인자·core 도입.
Match data mapping & types
src/pages/match/hooks/mapMatchData.ts, src/pages/match/match.tsx, src/shared/types/match-types.ts, src/shared/components/card/match-card/types/card.ts
isCreated 필드 타입·매핑에 추가. 1:1 응답 키 matesresults 반영. 칩 키 정규화(normalizeChipKey) 및 isChipColor 필터 적용.
Creator crown & theme
src/shared/components/card/match-card/components/card-header.tsx, src/shared/styles/theme.css
카드 생성자 표시용 왕관 오버레이 구현(isCreated 사용), 왕관 크기/위치 로직 추가. 테마에 --color-owner--z-card-owner 추가.
Card/profile rendering & constants
src/shared/components/card/match-card/components/card-profile-image.tsx, src/shared/components/card/constants/MATCH.ts, src/shared/components/card/match-card/utils/normalize-urls.ts, src/shared/components/card/match-card/styles/card-variants.ts
imgUrl 입력 일반화(normalizeUrls), PROFILE_SLOT_COUNT 기반 슬롯 렌더링, 이미지 lazy/decoding/object-fit 적용, 그룹 슬롯 placeholder 도입, profileVariants에서 border 제거, GROUP_MAXPROFILE_SLOT_COUNT 추가.
Tab style refactor
src/shared/components/tab/tab/styles/tab-style.ts, src/shared/components/tab/tab/tab-item.tsx
탭 스타일맵 키명 변경(borderThicknessborderStyle, typographytextStyle) 및 match 탭을 after-pseudo-element 기반 인디케이터로 전환; TabItem에서 키 사용 업데이트.
API endpoint update
src/shared/constants/api.ts
매칭 상태 조회 엔드포인트 버전 v1 → v2로 변경(GET_SINGLE_STATUS, GET_GROUP_STATUS).
Removed mocks & hook
src/shared/mocks/*, src/pages/match/hooks/useMate.ts
여러 mock 파일(src/shared/mocks/mockMatchData.ts, src/shared/mocks/mockMatchReceiveData.ts, src/shared/mocks/matchCardData.ts)과 useMate 훅 제거 및 관련 export 삭제.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as 사용자
  participant C as MatchCard UI
  participant S as match-status.getPendingToast
  participant T as showErrorToast
  participant P as patchStageMutation
  participant R as Router

  U->>C: 카드 클릭
  C->>S: getPendingToast(status, type)
  S-->>C: toastMsg (있음/없음)

  alt toastMsg 있음
    C->>T: showErrorToast(메시지, offset?, icon?)
    T-->>C: 토스트 표시
    C-->>U: 네비게이션 중단
  else toastMsg 없음
    alt status === '승인 완료'
      C->>P: patchStageMutation.mutateAsync(card.id)
      P-->>C: 성공/오류
      Note right of C: 오류는 콘솔 로깅
    end
    C-->>R: navigate(ROUTES.RESULT, query: { cardId, cardType, tab })
    R-->>U: 경로 변경
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Assessment against linked issues

Objective Addressed Explanation
매칭 생성자 프로필에 왕관을 표시합니다. ( #316 )
1:1 / 그룹탭 바를 수정합니다. ( #316 )
요청/승인 대기 중 토스트를 추가합니다. ( #316 )
매칭 완료/실패 시 카드를 삭제합니다. ( #316 ) 카드 삭제 또는 상태 업데이트 로직(삭제 후 리무브 처리)이 PR에 포함되어 있지 않음.

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Mock 파일 제거 (src/shared/mocks/mockMatchData.ts, src/shared/mocks/mockMatchReceiveData.ts, src/shared/mocks/matchCardData.ts) 이슈 #316 목표 항목에는 mock 삭제가 명시되지 않음; 테스트/데이터 제거로 보여 의도 확인 필요.
useMate 훅 제거 (src/pages/match/hooks/useMate.ts) 이슈 요구사항(왕관/탭/토스트/배지/카드 삭제)과 직접 관련되지 않음—사용자 데이터 훅 제거 여부 확인 권장.
API 엔드포인트 버전 전환 (src/shared/constants/api.ts) 이슈 설명에 v2 마이그레이션이 명시되지 않음; 외부 통합 변경으로 보이며 이슈 범위와 직접 연관성 불명.

Possibly related PRs

Suggested reviewers

  • yeeeww
  • heesunee
  • bongtta

Poem

"왕관 쓴 토끼가 깡총 인사해요 👑
클릭 전에 '잠깐!' 토스트가 속삭이고,
승인이면 패치하고 결과로 달려가요,
탭은 반짝이고 모의 데이터는 숨었답니다,
토끼가 축하해요, 깡총깡총! 🐇"


📜 Recent 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 f244bf0 and 32e8f12.

📒 Files selected for processing (2)
  • src/pages/match/hooks/mapMatchData.ts (1 hunks)
  • src/shared/components/card/match-card/utils/normalize-urls.ts (1 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#316/match-status

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

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • 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

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.

@github-actions github-actions bot added feat new feature 소은 labels Aug 31, 2025
@github-actions
Copy link

MATEBALL-STORYBOOK
⚾ Storybook 배포가 완료되었습니다!

Copy link

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/shared/types/match-types.ts (1)

30-39: 중복 인터페이스 이름(singleMatchMate) 병합 문제 — 명확한 분리/리네이밍 필요

동일 이름의 인터페이스가 두 번 선언되어 TS 인터페이스 병합이 일어나며, 리스트/현황의 서로 다른 스키마가 섞입니다(예: matchRate 필드가 현황에도 강제됨). 이는 타입 신뢰성을 크게 떨어뜨립니다. 현황 전용 타입을 분리/리네임하고 참조를 업데이트해 주세요.

-// 1:1 매칭 현황에 사용되는 Mate
-// @extends baseMate
-export interface singleMatchMate extends baseMate {
+// 1:1 매칭 현황에 사용되는 Mate
+// 리스트 타입과 혼동을 피하기 위해 분리
+export interface singleMatchStatusMate extends baseMate {
   age: string;
   gender: string;
   team: string;
   style: string;
   /** 매칭 상태 (ex. 승인대기중, 요청대기중 등) */
   status: string;
   imgUrl: string;
 }

그리고 응답 타입에서 사용처도 교체가 필요합니다(아래 제안 참조).

Also applies to: 65-73

src/pages/match/components/match-tab-pannel.tsx (1)

70-75: 중요: props 스프레드 순서로 인한 color/chipColor 덮어쓰기

{...card}가 마지막에 와서, 상단에서 계산한 color/chipColor가 카드 객체의 값으로 덮어씌워질 수 있습니다. 의도대로 상태 기반 색상이 우선되도록 순서를 교체하세요.

-            <Card
-              status={card.status}
-              color={getCardColor(card.status)}
-              chipColor={getColorType(card.status)}
-              {...card}
-            />
+            <Card
+              {...card}
+              status={card.status}
+              color={getCardColor(card.status)}
+              chipColor={getColorType(card.status)}
+            />
🧹 Nitpick comments (20)
src/shared/styles/theme.css (3)

30-30: 새 색상 토큰 추가 OK — 사용처 확인 요청

--color-owner 정의 좋습니다. 실제 컴포넌트에서 참조되는지(크라운/오너 배지)와 명암 대비(텍스트/아이콘 대비 ≥ 4.5:1)만 확인 부탁드립니다.


80-80: z-index 계층 충돌 가능성 점검

--z-card-owner: 5가 카드 내부 프로필(1~4) 위에 오도록 잘 잡혔습니다. 다만 전역 값(--z-under-header-section: 5)과 동일하여 스택 컨텍스트에 따라 예기치 못한 교차가 날 수 있어, 카드 내부에서만 사용하는 값이면 컴포넌트 단에서 신규 스택 컨텍스트(position/transform) 생성 여부를 확인해 주세요.


101-105: CSS 변수 오타 의심: --etc-overlay → --color-overlay

현재 fallback으로 동작해 눈치채기 어려울 수 있습니다. 명시적 변수 사용을 권장합니다.

-    background: var(--etc-overlay, rgba(0, 0, 0, 0.3));
+    background: var(--color-overlay, rgba(0, 0, 0, 0.3));
src/shared/types/match-types.ts (1)

184-191: 주석/이름 불일치

블록 제목은 “1:1 매칭 현황 조회”인데 경로가 group로 표기되어 있습니다. 오타 수정 및 인터페이스 명도 응답 성격에 맞게 정리해 주세요.

src/shared/utils/show-error-toast.tsx (1)

19-19: Tailwind 우선순위 표기 오류

min-h-[4.5rem]!은 무효 클래스입니다. !min-h-[4.5rem]로 수정해야 합니다.

-      'min-h-[4.5rem]! max-w-[calc(43rem-3.2rem)] w-[calc(100%-3.2rem)] cap_14_m text-gray-white rounded-[12px] bg-gray-900',
+      '!min-h-[4.5rem] max-w-[calc(43rem-3.2rem)] w-[calc(100%-3.2rem)] cap_14_m text-gray-white rounded-[12px] bg-gray-900',
src/shared/components/tab/tab/styles/tab-style.ts (1)

13-23: 매치 탭 하단 인디케이터 전환 OK — after:content 클래스 JIT 안전성 확인

after 기반 인디케이터로 일관성 좋아졌습니다. Tailwind 설정에서 after:content-[""]가 퍼지되지 않도록 safelist 또는 고정 문자열 사용을 확인해 주세요.

src/shared/components/card/match-card/components/card-profile-image.tsx (1)

32-32: 그룹 프로필 아이콘 색상 클래스 제거됨 — 테마 상속/명도 대비 체크 부탁

명시 색상을 제거해 상위 컨텍스트의 글자색을 상속합니다. 다크 모드/특정 배경에서 아이콘 대비가 흐려질 수 있으니 회귀 여부를 한번 확인해 주세요. 아이콘이 장식 목적이라면 접근성도 함께 보완하면 좋겠습니다.

다음처럼 장식 아이콘임을 명시하는 정도는 부담 없이 추가 가능합니다:

-          <Icon size={2.8} name="profile" className={cn('rounded-full')} />
+          <Icon size={2.8} name="profile" aria-hidden className={cn('rounded-full')} />
src/shared/components/tab/tab/tab-item.tsx (1)

13-22: 탭 접근성 속성 보완 제안

WAI-ARIA 탭 패턴을 따른다면 role/tablist 컨텍스트에서 버튼에 aria-selected와 적절한 id/aria-controls 연결이 필요합니다(키보드 사용자 경험 개선).

가능하면 다음과 같이 속성을 보완해 주세요(실제 TabList의 역할/DOM 구조에 맞춰 조정):

-    <button
+    <button
       type="button"
       data-active={isActive}
       onClick={onClick}
+      role="tab"
+      aria-selected={isActive}
+      // id와 aria-controls는 TabList에서 부여/조합 중이면 생략 가능
+      // id={`tab-${label}`}
+      // aria-controls={`panel-${label}`}
src/shared/components/card/match-card/styles/card-variants.ts (1)

25-29: 프로필 보더 제거로 중첩 썸네일 경계 흐림 가능성 — UI 의도라면 OK, 아니면 ring 제안

프로필 variant에서 테두리를 제거해 그룹 썸네일(겹침) 경계가 배경에 따라 덜 구분될 수 있습니다. 의도된 미니멀 변경이면 OK이고, 경계가 필요하면 그룹에만 얇은 ring을 주는안을 고려해 보세요.

예시:

 export const profileVariants = cva('overflow-hidden rounded-full object-cover', {
   variants: {
     type: {
       single: 'h-[6rem] w-[6rem]',
-      group: 'h-[2.8rem] w-[2.8rem]',
+      group: 'h-[2.8rem] w-[2.8rem] ring-1 ring-white',
       detailed: 'h-[8.2rem] w-[8.2rem]',
       user: 'h-[8.2rem] w-[8.2rem]',
     },
   },
src/pages/match/hooks/mapMatchData.ts (2)

8-16: chips 매핑의 안전성 소폭 보강 제안

team/style 값이 빈 문자열/undefined일 가능성이 있다면 필터링을 거쳐 넣는 편이 안전합니다.

예시:

chips: [mate.team, mate.style].filter(Boolean) as ChipColor[],

이미 데이터 보장이 확실하다면 현 상태 유지해도 무방합니다.


10-13: 싱글 이미지 URL 처리 시 빈 값 대응 고려

mate.imgUrl이 없을 때 빈 배열로 두거나 플레이스홀더를 지정하면 다운스트림에서 string[] 가정과의 타입/표현 이슈를 줄일 수 있습니다.

예시:

imgUrl: mate.imgUrl ? [mate.imgUrl] : [],
src/pages/match/utils/match-status.ts (4)

22-34: 대기 토스트 매핑: 부분 일치 허용 + 반환 타입 단순화 제안

API에서 상태 문자열에 보조 텍스트가 붙을 가능성(예: "승인 대기 중(2/4)")을 고려해 includes로 매칭하고, 빈 문자열 대신 undefined를 반환하도록 단순화하면 호출부 가독성과 안정성이 좋아집니다.

-export const getPendingToast = (
+export const getPendingToast = (
   status?: string,
   type?: MatchableCardProps['type'],
-): string | '' => {
-  if (!status) return '';
-  if (status === '요청 대기 중') return '메이트의 요청을 기다리는 중입니다.';
-  if (status === '승인 대기 중') {
+): string | undefined => {
+  if (!status) return undefined;
+  if (status.includes('요청 대기')) return '메이트의 요청을 기다리는 중입니다.';
+  if (status.includes('승인 대기')) {
     return type === 'group'
       ? '메이트 전원의 승인을 기다리는 중입니다.'
       : '메이트의 승인을 기다리는 중입니다.';
   }
-  return '';
+  return undefined;
 }

18-18: 탭 아이템 리터럴 타입 보존(as const) 제안

사용처에서 튜플 리터럴로 취급하면 오타 방지 및 추론 품질이 올라갑니다. 영향도는 낮으니 필요 시에만 적용하세요.

-export const fillTabItems = ['전체', '대기 중', '완료', '실패'];
+export const fillTabItems = ['전체', '대기 중', '완료', '실패'] as const;

10-16: 실패 외 '취소' 상태도 비활성 처리 포함 여부

상태 텍스트에 '취소'가 올 수 있다면 inactive에 포함하는 편이 자연스럽습니다.

-  if (status.includes('대기') || status.includes('실패')) {
+  if (status.includes('대기') || status.includes('실패') || status.includes('취소')) {
     return 'inactive';
   }

1-1: MatchableCardProps 중복 정의 정리

동일한 MatchableCardProps가 이 파일과 match-tab-pannel.tsx 양쪽에 존재합니다. 한 곳(예: utils)에서 export하여 재사용하면 타입 드리프트를 방지할 수 있습니다.

Also applies to: 20-20

src/shared/components/card/match-card/types/card.ts (1)

28-39: 중복 속성 제거: 하위 인터페이스의 isCreated 재선언 불필요

Single/Group/Detailed/User에서 isCreated를 중복 선언하고 있습니다. BaseCardProps에 이미 존재하므로 중복은 유지보수 리스크입니다(타입 변경시 드리프트). 하위 인터페이스의 중복 선언을 제거하세요.

 export interface SingleCardProps extends BaseCardProps {
   type: 'single';
   age: string;
   gender: string;
   color?: 'active' | 'inactive';
   chipColor?: ChipColorType;
   chips?: ChipColor[];
   team: string;
   style: string;
   matchRate?: number;
-  isCreated: boolean;
 }

 export interface GroupCardProps extends BaseCardProps {
   type: 'group';
   count: number;
   color?: 'active' | 'inactive';
   matchRate?: number;
-  isCreated: boolean;
 }

 export interface DetailedCardProps extends BaseCardProps {
   type: 'detailed';
   age: string;
   gender: string;
   introduction: string;
   matchRate: number;
   chips: ChipColor[];
   team: string;
   style: string;
-  isCreated: boolean;
 }

 export interface UserCardProps {
   type: 'user';
   nickname: string;
   imgUrl: string[];
   team: string;
   style: string;
   age: string;
   gender: string;
   introduction: string;
   className?: string;
   color?: 'active' | 'inactive';
   chips: ChipColor[];
-  isCreated: boolean;
 }

Also applies to: 41-47, 49-59, 61-74

src/pages/match/components/match-tab-pannel.tsx (1)

39-46: 연타 방지 및 실패 토스트 보강 제안

'승인 완료'에서 mutate 중 연타 시 중복 호출 위험이 있습니다. 진행 중 가드와 실패 토스트를 추가하세요.

   try {
-      if (card.status === '승인 완료') {
-        await patchStageMutation.mutateAsync(card.id);
-      }
+      if (card.status === '승인 완료') {
+        if (patchStageMutation.isPending) return;
+        await patchStageMutation.mutateAsync(card.id);
+      }
       navigate(`${ROUTES.RESULT(card.id.toString())}?type=${query}&cardtype=${card.type}`);
   } catch (error) {
-      console.error('매칭 상태 전환 실패', error);
+      console.error('매칭 상태 전환 실패', error);
+      showErrorToast('매칭 상태 전환에 실패했어요. 잠시 후 다시 시도해 주세요.', { icon: false });
   }
src/shared/components/card/match-card/components/card-header.tsx (3)

15-29: 마이크로: 스펙 상수 외부 추출로 재생성 최소화

getCrownSpec은 렌더마다 새 객체를 생성합니다. 상수 맵을 컴포넌트 밖으로 빼면 미세하지만 불필요한 할당을 줄일 수 있습니다.

// 컴포넌트 바깥
const CROWN_SPEC = {
  group: { box: 'h-[1.2rem] w-[1.2rem]', pos: 'left-[1.4rem] -bottom-[0.2rem]', size: 1.2 as const },
  default: { box: 'h-[1.6rem] w-[1.6rem]', pos: 'right-[0.3rem] -bottom-[0.2rem]', size: 1.6 as const },
} as const;

// 내부
const getCrownSpec = (t: CardProps['type']) => (t === 'group' ? CROWN_SPEC.group : CROWN_SPEC.default);

80-81: 그룹 인원 표기에서 음수 방지

count가 0/undefined인 경우 -1명 표기가 될 수 있습니다. 0 하한을 두세요.

-              {props.nickname} 외 {(props.count ?? 1) - 1}명
+              {props.nickname} 외 {Math.max((props.count ?? 1) - 1, 0)}명

84-85: 표시 범위 클램프(선택)

상단과 동일하게 표시 안정성을 위해 현재 인원을 0~GROUP_MAX로 클램프하는 것을 고려하세요.

-                매칭된 인원 {props.count ?? 1}/{GROUP_MAX}
+                매칭된 인원 {Math.min(Math.max(props.count ?? 0, 0), GROUP_MAX)}/{GROUP_MAX}
📜 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 492cbea and 19ec841.

⛔ Files ignored due to path filters (1)
  • src/shared/assets/svgs/crown.svg is excluded by !**/*.svg
📒 Files selected for processing (14)
  • src/pages/match/components/match-tab-pannel.tsx (4 hunks)
  • src/pages/match/hooks/mapMatchData.ts (2 hunks)
  • src/pages/match/match.tsx (1 hunks)
  • src/pages/match/utils/match-status.ts (2 hunks)
  • src/shared/components/card/match-card/components/card-header.tsx (6 hunks)
  • src/shared/components/card/match-card/components/card-profile-image.tsx (1 hunks)
  • src/shared/components/card/match-card/styles/card-variants.ts (1 hunks)
  • src/shared/components/card/match-card/types/card.ts (4 hunks)
  • src/shared/components/tab/tab/styles/tab-style.ts (1 hunks)
  • src/shared/components/tab/tab/tab-item.tsx (1 hunks)
  • src/shared/constants/api.ts (1 hunks)
  • src/shared/styles/theme.css (2 hunks)
  • src/shared/types/match-types.ts (3 hunks)
  • src/shared/utils/show-error-toast.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
src/pages/match/match.tsx (1)
src/pages/match/hooks/mapMatchData.ts (1)
  • mapSingleMatchData (8-16)
src/shared/components/card/match-card/components/card-profile-image.tsx (1)
src/shared/libs/cn.ts (1)
  • cn (4-6)
src/shared/components/tab/tab/tab-item.tsx (1)
src/shared/libs/cn.ts (1)
  • cn (4-6)
src/shared/components/card/match-card/components/card-header.tsx (4)
src/shared/components/card/match-card/types/card.ts (1)
  • CardProps (76-76)
src/shared/routes/routes-config.ts (1)
  • ROUTES (1-22)
src/shared/libs/cn.ts (1)
  • cn (4-6)
src/shared/components/card/constants/MATCH.ts (1)
  • GROUP_MAX (1-1)
src/pages/match/utils/match-status.ts (1)
src/shared/components/card/match-card/types/card.ts (2)
  • SingleCardProps (28-39)
  • GroupCardProps (41-47)
src/pages/match/components/match-tab-pannel.tsx (3)
src/pages/match/utils/match-status.ts (1)
  • getPendingToast (22-34)
src/shared/utils/show-error-toast.tsx (1)
  • showErrorToast (9-21)
src/pages/match/constants/matching.ts (1)
  • CLICKABLE_STATUS_MAP (43-47)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: chromatic
🔇 Additional comments (15)
src/shared/constants/api.ts (1)

33-34: v2 엔드포인트 전환 OK — 다운스트림 호환성 점검

응답 스키마 변경(results로의 교체 등)이 반영된 모든 호출부가 업데이트됐는지 확인 바랍니다.

src/shared/types/match-types.ts (2)

179-181: results 키 전환 OK — 사용처 전역 치환 확인

현황 싱글 응답의 컬렉션 키를 mates→results로 바꾼 점 좋습니다. 모든 호출부(mapper, 페이지네이션, 빈 상태 처리 등)에서 필드명이 일관되는지 점검 부탁드립니다.

추가로, 위 병합 이슈 해결을 위해 타입을 아래처럼 교체 권장:

-export interface getSingleMatchStatusResponse {
-  results: singleMatchMate[];
-}
+export interface getSingleMatchStatusResponse {
+  results: singleMatchStatusMate[];
+}

197-208: 그룹 현황 isCreated 추가 OK

UI 크라운 표기를 위한 플래그 추가가 명확합니다. BE 스키마 반영 여부만 확인 부탁드립니다.

src/shared/utils/show-error-toast.tsx (1)

4-7: 옵션 객체 도입 좋습니다

아이콘/오프셋을 옵션으로 통합한 점 깔끔합니다.

src/shared/components/tab/tab/styles/tab-style.ts (1)

8-11: 키 네이밍 정리(home): OK

borderThickness→borderStyle, typography→textStyle 변경 합리적입니다. 사용처(tab-item) 동기화만 확인 부탁드립니다.

src/shared/components/tab/tab/tab-item.tsx (1)

20-21: 스타일 키 변경 반영 적절함

borderThickness→borderStyle, typography→textStyle 변경을 정확히 반영했습니다. 다른 사용처도 동일 키로 업데이트되어 있는지만 한 번만 스캔 부탁드립니다.

Also applies to: 26-27

src/pages/match/match.tsx (1)

28-28: 싱글 경로 'mates' 잔여 참조 없음 확인 — 변경 사항 승인

src/pages/match/hooks/mapMatchData.ts (2)

13-14: isCreated 전파 처리 적절함

불리언 캐스팅으로 안전하게 전파되었습니다. 카드 헤더(왕관 렌더)와 연동에도 문제 없어 보입니다.


22-23: 그룹 경로도 isCreated 전파 OK

그룹 카드에도 동일하게 반영되어 일관성 있습니다.

src/pages/match/utils/match-status.ts (1)

3-8: '승인 완료'의 탭 분류 의도 확인 필요

현재 로직상 '승인 완료'는 '완료'가 아닌 '대기 중'으로 분류됩니다(부분 일치 규칙에 걸리지 않음). 기획 상 의도된 분류인지 확인 부탁드립니다.

src/shared/components/card/match-card/types/card.ts (2)

24-26: Base에 isCreated 추가는 적절합니다

헤더에서 크라운 렌더링 조건을 공통 속성으로 들고 가는 방향 👍


61-74: UserCard에도 isCreated가 필요한지 확인

'유저 카드' 맥락에서 생성자 크라운이 실제로 노출되는 시나리오가 있는지 확인이 필요합니다. 없다면 UserCard의 필수 속성에서 제외해도 됩니다(헤더에서 접근하지 않는다면).

src/pages/match/components/match-tab-pannel.tsx (2)

30-34: 대기 상태 클릭 토스트 게이팅 추가 👍

요구사항(요청/승인 대기 토스트) 충족하며 기존 네비게이션 플로우에 영향이 없습니다.


64-69: 접근성: aria-disabled 사용 적절

비클릭 항목에 aria-disabled를 부여하면서 클릭은 핸들러에서 게이팅해 토스트를 노출하는 UX가 유지됩니다. 합리적입니다.

src/shared/components/card/match-card/components/card-header.tsx (1)

30-49: 크라운 오버레이 구현 깔끔합니다

isolate+pointer-events-none로 클릭 히트영역을 침범하지 않고, 타입별 스펙 분기와 접근성(aria-hidden)도 적절합니다.

@MATEBALL MATEBALL deleted a comment from coderabbitai bot Aug 31, 2025
@MATEBALL MATEBALL deleted a comment from coderabbitai bot Aug 31, 2025
@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Aug 31, 2025

Deploying mateball-client with  Cloudflare Pages  Cloudflare Pages

Latest commit: 32e8f12
Status: ✅  Deploy successful!
Preview URL: https://9feac3ee.mateball-client.pages.dev
Branch Preview URL: https://feat--316-match-status.mateball-client.pages.dev

View logs

Copy link

@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

♻️ Duplicate comments (1)
src/shared/utils/show-error-toast.tsx (1)

18-21: offsetBottom 옵션 객체 호환 누락 — 레그레션 위험

isShowToastOptionsoffsetBottom 키를 감지하지만, 실제 분기(라인 33–38)에서 arg.offsetBottom을 반영하지 않아 기존 호출부(showErrorToast(msg, { offsetBottom: '...' }))가 무시됩니다. 하위 호환을 위해 타입과 매핑을 보완해 주세요.

적용 diff:

 type ShowToastOptions = {
   icon?: boolean;
-  bottom?: string;
+  bottom?: string;
+  // backward-compat
+  offsetBottom?: string;
 };

 const isShowToastOptions = (v: unknown): v is ShowToastOptions => {
   if (v === null || typeof v !== 'object') return false;
   return 'icon' in v || 'offsetBottom' in v || 'bottom' in v;
 };

 export const showErrorToast: ShowErrorToastFn = (message, arg?) => {
   let iconEnabled = true;
   let bottom = '8.3rem';

   if (typeof arg === 'string') {
     bottom = arg;
   } else if (isShowToastOptions(arg)) {
     if (typeof arg.icon === 'boolean') iconEnabled = arg.icon;
-    bottom = arg.bottom ?? bottom;
+    bottom = arg.bottom ?? arg.offsetBottom ?? bottom;
   }

추가로, 레거시 사용 여부를 점검해 주세요:

#!/bin/bash
# 레거시 객체형 호출 검색
rg -nP --type=ts --type=tsx -C2 'showErrorToast\([^,]+,\s*\{[^}]*offsetBottom\s*:'

Also applies to: 33-38

🧹 Nitpick comments (6)
src/shared/components/card/match-card/types/card.ts (5)

25-25: isCreated 추가는 요구사항과 정합, 하위 타입 중복 제거를 권장

왕관 표시(생성자 플래그) 목적에 부합합니다. DRY 관점에서 Single/Group/Detailed 쪽의 동일 필드 선언은 제거해 주세요. 또한 의미 혼동 방지를 위해 짧은 JSDoc을 붙이는 것을 권장합니다.

예:

/** 현재 로그인 사용자가 생성한 매치 카드 여부(왕관 표시 제어) */
isCreated?: boolean;

38-38: 상속으로 이미 포함된 isCreated 중복 선언 제거

BaseCardProps에 있으므로 여기서는 중복입니다. 삭제해 주세요.

-  isCreated?: boolean;

46-46: 상속으로 이미 포함된 isCreated 중복 선언 제거

BaseCardProps 경유로 포함됩니다. 중복 라인 삭제 권장.

-  isCreated?: boolean;

58-58: 상속으로 이미 포함된 isCreated 중복 선언 제거

Detailed도 BaseCardProps 상속으로 충분합니다.

-  isCreated?: boolean;

73-73: UserCardProps의 isCreated 유지 OK. 다만 공통 믹스인으로 추출 시 중복 제거 가능

UserCardProps는 BaseCardProps를 상속하지 않으므로 현재 선언은 타당합니다. 선택적으로 공통 믹스인 타입을 도입해 중복을 없앨 수 있습니다.

  • 변경(선택): 공통 믹스인 도입
export interface WithOwnerFlag {
  /** 현재 로그인 사용자가 생성한 엔티티 여부 */
  isCreated?: boolean;
}

// BaseCardProps와 UserCardProps가 확장
export interface BaseCardProps extends WithOwnerFlag { ... }
export interface UserCardProps extends WithOwnerFlag { ... }
  • 위와 같이 적용 시, 본 라인은 제거:
-  isCreated?: boolean;
src/shared/utils/show-error-toast.tsx (1)

10-10: 뷰포트 단위 확장 제안(dvh/svh/lvh 지원)

모바일 환경 대응을 위해 isValidLendvh|svh|lvh 단위를 추가하면 안전합니다.

-const isValidLen = (v: string) => /^-?\d+(\.\d+)?(rem|px|vh|vw|%)$/.test(v);
+const isValidLen = (v: string) =>
+  /^-?\d+(\.\d+)?(rem|px|vh|dvh|svh|lvh|vw|%)$/.test(v);
📜 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 19ec841 and 99b1529.

📒 Files selected for processing (6)
  • src/pages/match/hooks/useMate.ts (0 hunks)
  • src/shared/components/card/match-card/types/card.ts (4 hunks)
  • src/shared/mocks/matchCardData.ts (0 hunks)
  • src/shared/mocks/mockMatchData.ts (0 hunks)
  • src/shared/mocks/mockMatchReceiveData.ts (0 hunks)
  • src/shared/utils/show-error-toast.tsx (1 hunks)
💤 Files with no reviewable changes (4)
  • src/shared/mocks/mockMatchData.ts
  • src/shared/mocks/matchCardData.ts
  • src/shared/mocks/mockMatchReceiveData.ts
  • src/pages/match/hooks/useMate.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/shared/utils/show-error-toast.tsx (1)
src/shared/libs/cn.ts (1)
  • cn (4-6)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: chromatic
🔇 Additional comments (1)
src/shared/utils/show-error-toast.tsx (1)

23-27: 오버로드 시그니처 도입 좋습니다

문자열/옵션 객체/미지정 세 케이스를 정확히 캡슐화했습니다. 이후 호출부 변경 비용을 줄일 수 있습니다.

@MATEBALL MATEBALL deleted a comment from coderabbitai bot Aug 31, 2025
@MATEBALL MATEBALL deleted a comment from coderabbitai bot Aug 31, 2025
Copy link

@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

🧹 Nitpick comments (8)
src/pages/match/hooks/mapMatchData.ts (2)

9-10: 스타일 키 공백 정규화 좋습니다. 팀 키에도 동일 처리 고려

팀 명에도 예외적 공백이 들어오는 경우를 방어하려면 팀 키도 정규화하면 안전합니다.

다음처럼 변경을 제안합니다:

-    const teamKey = mate.team;
+    const teamKey = normalizeChipKey(mate.team);

21-27: 빈 칩 처리 시 UI 의도 확인(기본값/생략 중 택1)

[teamKey, styleKey]가 모두 매치되지 않으면 빈 배열이 전달됩니다. UI가 빈 칩 영역을 렌더링한다면 undefined로 생략하거나 기본 칩을 지정하는 쪽이 나을 수 있습니다.

선호안 A(생략):

-      chips,
+      chips: chips.length ? chips : undefined,

선호안 B(기본값): 제품 의도에 맞는 기본 키(예: 'default')가 있다면 그 값을 설정.

src/shared/components/card/match-card/components/card-profile-image.tsx (6)

24-24: key 충돌 가능성 제거(중복 URL·서버 리사이즈 쿼리 차이 등)

URL을 key로 쓰면 중복/변형(쿼리스트링)으로 불안정할 수 있습니다. order 기반으로 고정하세요.

-key={hasSrc ? url : `profile-slot-${order}`}
+key={`profile-slot-${order}`}

31-41: 접근성: 대체 텍스트 보강

의미 있는 프로필 이미지라면 빈 alt는 부적절합니다. 최소한의 일반 텍스트로 보강하세요(추후 이름 전달 시 치환 권장).

-  alt=""
+  alt="프로필 이미지"

46-55: 의심스러운 유틸 클래스 사용(flex-row-center)

Tailwind 기본 클래스가 아니며 프로젝트 전역 유틸이 아닐 경우 적용 실패합니다. 안전한 조합으로 교체 권장.

- className={cn(
-   'flex-row-center overflow-hidden rounded-full',
+ className={cn(
+   'flex items-center justify-center overflow-hidden rounded-full',
    profileVariants({ type }),
    zIndexClasses[slotIndex],
 )}

57-57: 중복 사이즈 지정 제거

부모가 이미 원형 사이즈를 갖습니다. 내부 플레이스홀더는 꽉 채우도록 간소화하세요.

-<div className="h-[2.8rem] w-[2.8rem] rounded-full bg-gray-400" />
+<div className="h-full w-full rounded-full bg-gray-400" />

78-84: Icon API 사용 일관화(size vs width/height) 및 컨테이너 적응

group에서는 size, 단건에서는 width/height를 사용하고 있어 크기 불일치 위험이 있습니다. 컨테이너 크기에 맞추도록 아이콘을 채우는 방식으로 정리하세요.

-<Icon
-  width={6}
-  height={6}
-  name="profile"
-  className={cn('overflow-hidden rounded-full', profileVariants({ type }))}
-/>
+<Icon
+  name="profile"
+  className={cn('h-full w-full overflow-hidden rounded-full', profileVariants({ type }))}
+  aria-label="프로필 아이콘"
/>

26-29: profileVariants의 object-cover 중복 적용

현재 컨테이너/이미지 양쪽에 object-cover가 들어갑니다. 시각적 영향은 없지만 역할상 이미지에만 필요합니다. variants 정의(또는 컨테이너 적용부)에서 중복 제거를 검토해 주세요.

Also applies to: 75-76

📜 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 47c541e and dcaaf59.

📒 Files selected for processing (2)
  • src/pages/match/hooks/mapMatchData.ts (1 hunks)
  • src/shared/components/card/match-card/components/card-profile-image.tsx (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/shared/components/card/match-card/components/card-profile-image.tsx (2)
src/shared/components/card/match-card/types/card.ts (1)
  • CardProfileProps (78-81)
src/shared/components/card/match-card/styles/card-variants.ts (1)
  • profileVariants (22-34)
src/pages/match/hooks/mapMatchData.ts (3)
src/shared/components/card/match-card/types/card.ts (3)
  • ChipColor (8-8)
  • SingleCardProps (28-39)
  • GroupCardProps (41-47)
src/shared/components/chip/styles/chip-variants.ts (1)
  • chipVariantOptions (3-36)
src/shared/types/match-types.ts (3)
  • singleMatchMate (30-39)
  • singleMatchMate (65-73)
  • getGroupMatchMate (197-208)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (4)
src/pages/match/hooks/mapMatchData.ts (4)

6-6: chipVariantOptions 의존 추가 적절

칩 색상 키 소스에 직접 의존하도록 바뀌어 런타임/타입 가드 모두 안정적입니다.


11-13: 타입 가드 구현 타당

hasOwnProperty.call로 프로토타입 체인 오염을 회피하고 있어 안전합니다. filter(isChipColor)의 타입 내로잉도 잘 동작합니다.


31-36: 그룹 매핑에서 isCreated 캐스팅 및 타입 지정 적절

스프레드 후 type: 'group'과 불리언 캐스팅 모두 명확합니다.


15-28: singleMatchMate에 isCreated 포함 확인됨
singleMatchMate이 상위 타입인 matchRateMate를 통해 isCreated: boolean을 상속하므로, 매핑 로직에 문제 없습니다.

Comment on lines +9 to +13
const normalizeChipKey = (v?: string) => (v ?? '').replace(/\s/g, '');

const isChipColor = (k: string): k is ChipColor =>
Object.prototype.hasOwnProperty.call(chipVariantOptions.bgColor, k);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

api 응답에서 직관 스타일에 띄어쓰기가 포함되어 '열정 응원러'처럼 와서 그동안 스타일 칩이 나타나지 않는 거였어요! 그래서 이렇게 띄어쓰기에도 대응할 수 있도록 추가했습니다.

Copy link
Contributor

Choose a reason for hiding this comment

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

마찬가지로 utils로 이동하면 좋을 것 같아요!

Comment on lines +22 to +34
export const getPendingToast = (
status?: string,
type?: MatchableCardProps['type'],
): string | '' => {
if (!status) return '';
if (status === '요청 대기 중') return '메이트의 요청을 기다리는 중입니다.';
if (status === '승인 대기 중') {
return type === 'group'
? '메이트 전원의 승인을 기다리는 중입니다.'
: '메이트의 승인을 기다리는 중입니다.';
}
return '';
};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

요청 상태에 따라 토스트 메시지를 다르게 보여주는 목적입니다!

@MATEBALL MATEBALL deleted a comment from coderabbitai bot Sep 1, 2025
Copy link

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/shared/components/card/match-card/components/card-header.tsx (1)

97-114: ChipList 입력 안정성 통일 (detailed 케이스 누락)

다른 케이스는 props.chips ?? []로 널 세이프 처리했는데, detailed만 누락되었습니다. 컴포넌트 내부가 배열을 기대하면 런타임 이슈가 납니다.

-              <ChipList names={props.chips} />
+              <ChipList names={props.chips ?? []} />
🧹 Nitpick comments (2)
src/shared/components/card/match-card/components/card-header.tsx (2)

30-49: 오버레이 a11y 보강 + Icon 크기 클래스 적용

생성자 표시가 스크린리더에 전달되도록 aria를 추가하고, 위 제안과 함께 아이콘 크기를 클래스 기반으로 적용하세요.

-      <div className="relative isolate">
+      <div className="relative isolate">
         <CardProfile type={profileType} imgUrl={props.imgUrl} />
         {props.isCreated && (
-          <span
+          <span
             className={cn(
               'pointer-events-none absolute z-[var(--z-card-owner)]',
               spec.pos,
               'grid place-items-center rounded-full shadow-sm',
               spec.box,
             )}
+            role="img"
+            aria-label="매칭 생성자"
           >
-            <Icon name="crown" size={spec.size} className="text-owner" aria-hidden />
+            <Icon name="crown" className={cn('text-owner', spec.iconCls)} aria-hidden />
           </span>
         )}
       </div>

15-29: 숫자 size prop(px 단위) 확인 및 클래스 기반 sizing 권장
IconProps.size가 number일 때 px로 처리되어 1.2→1.2px로 렌더됩니다. rem 단위 크기를 사용하려면 문자열('1.2rem')을 전달하거나, Tailwind 클래스(h-[1.2rem] w-[1.2rem])를 직접 적용하는 iconCls/className 방식을 권장합니다.

src/shared/components/card/match-card/components/card-header.tsx
@@
   const getCrownSpec = (t: CardProps['type']) => {
     if (t === 'group') {
       return {
-        box: 'h-[1.2rem] w-[1.2rem]',
-        pos: 'left-[1.4rem] -bottom-[0.2rem]',
-        size: 1.2 as const,
+        box: 'h-[1.2rem] w-[1.2rem]',
+        pos: 'left-[1.4rem] -bottom-[0.2rem]',
+        iconCls: 'h-[1.2rem] w-[1.2rem]',
       };
     }
     return {
-      box: 'h-[1.6rem] w-[1.6rem]',
-      pos: 'right-[0.3rem] -bottom-[0.2rem]',
-      size: 1.6 as const,
+      box: 'h-[1.6rem] w-[1.6rem]',
+      pos: 'right-[0.3rem] -bottom-[0.2rem]',
+      iconCls: 'h-[1.6rem] w-[1.6rem]',
     };
   };
📜 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 dcaaf59 and 30255ba.

📒 Files selected for processing (1)
  • src/shared/components/card/match-card/components/card-header.tsx (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/shared/components/card/match-card/components/card-header.tsx (3)
src/shared/components/card/match-card/types/card.ts (1)
  • CardProps (76-76)
src/shared/routes/routes-config.ts (1)
  • ROUTES (1-22)
src/shared/libs/cn.ts (1)
  • cn (4-6)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: chromatic
🔇 Additional comments (2)
src/shared/components/card/match-card/components/card-header.tsx (2)

51-56: 중복 제거 훌륭합니다 — renderProfile 추출로 일관성/재사용성 향상

각 타입에서 공통 렌더 경로로 합쳐 crown 오버레이 관리도 단일화된 점 좋습니다.

Also applies to: 86-86, 100-100, 118-118


6-7: import 경로는 올바릅니다
실제 파일 경로가 src/shared/components/icon/icon.tsx이고, 디렉터리명·파일명이 모두 소문자 icon으로 되어 있어 현재 import Icon from '@components/icon/icon'; 경로가 정확합니다. 대문자 Icon으로 변경할 필요 없습니다.

Likely an incorrect or invalid review comment.

Copy link

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/shared/utils/show-error-toast.tsx (1)

4-14: showErrorToast 시그니처 수정: 옵션 객체 지원 및 빈 메시지 가드 추가

src/shared/utils/show-error-toast.tsx를 아래와 같이 업데이트하세요. PR 설명(showErrorToast(message, { icon: false }))과 일치하도록 옵션 객체 형태를 지원하고, 기존 (message, offset?, showIcon?) 호출을 하위호환하며, 빈 문자열 메시지로 토스트가 뜨지 않도록 가드를 추가했습니다. 호출부 검색 결과 객체 형태 호출은 없으나, 레거시 boolean 호출이 match-tab-pannel.tsx:32에서 확인되었습니다.

-export const showErrorToast = (message: string, offset?: string, showIcon: boolean = true) => {
-  toast({
-    text: message,
-    icon: showIcon ? <Icon name="error" className="mr-[0.4rem]" /> : undefined,
-    autoClose: 3000,
-    position: 'bottomCenter',
-    offset: offset ?? '8.3rem',
-    className:
-      'min-h-[4.5rem]! max-w-[calc(43rem-3.2rem)] w-[calc(100%-3.2rem)] cap_14_m text-gray-white rounded-[12px] bg-gray-900',
-  });
-};
+export const showErrorToast = (
+  message: string,
+  options?: { offset?: string; icon?: boolean } | string,
+  showIconLegacy: boolean = true
+): void => {
+  // 하위호환: (message, offset?, showIcon?) 서명도 계속 지원
+  const opts =
+    typeof options === 'string' || options === undefined
+      ? { offset: options as string | undefined, icon: showIconLegacy }
+      : options;
+
+  if (!message?.trim()) return; // 빈 토스트 방지
+
+  toast({
+    text: message,
+    icon: (opts.icon ?? true) ? <Icon name="error" className="mr-[0.4rem]" /> : undefined,
+    autoClose: 3000,
+    position: 'bottomCenter',
+    offset: opts.offset ?? '8.3rem',
+    className:
+      '!min-h-[4.5rem] max-w-[calc(43rem-3.2rem)] w-[calc(100%-3.2rem)] cap_14_m text-gray-white rounded-[12px] bg-gray-900',
+  });
+};
📜 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 30255ba and 8e39dc5.

📒 Files selected for processing (2)
  • src/pages/match/components/match-tab-pannel.tsx (4 hunks)
  • src/shared/utils/show-error-toast.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/match/components/match-tab-pannel.tsx
🔇 Additional comments (2)
src/shared/utils/show-error-toast.tsx (2)

7-7: 아이콘 토글 로직은 명확하고 적절합니다.

showIcon 플래그(또는 제안한 options.icon)에 따라 조건부로 렌더링하는 접근은 UI 일관성과 재사용성 측면에서 타당합니다.


9-9: 'bottomCenter'는 유효한 position 옵션입니다.
react-compact-toast의 허용 값(topLeft·topCenter·topRight·bottomLeft·bottomCenter·bottomRight) 중 하나로 확인되었습니다.

@MATEBALL MATEBALL deleted a comment from coderabbitai bot Sep 1, 2025
Copy link

@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)
src/shared/utils/show-error-toast.tsx (1)

10-10: offset 가드 보강(사소하지만 안전성 향상 제안)

빈 문자열/공백만 전달되는 경우를 방어하면 스타일 깨짐을 줄일 수 있습니다.

-    offset: offset ?? '8.3rem',
+    offset: typeof offset === 'string' && offset.trim() ? offset : '8.3rem',
📜 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 8e39dc5 and 20585b9.

📒 Files selected for processing (1)
  • src/shared/utils/show-error-toast.tsx (1 hunks)
🔇 Additional comments (2)
src/shared/utils/show-error-toast.tsx (2)

7-7: 아이콘 조건부 렌더링 로직은 적절합니다

undefined로 비활성화하는 접근은 라이브러리 기대치와도 부합합니다.


12-12: Tailwind !important 접두 위치 수정 LGTM

!min-h-[...]처럼 !을 유틸리티 앞에 두는 것이 정석입니다.

Copy link
Contributor

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

@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)
src/shared/utils/show-error-toast.tsx (1)

28-39: 옵션-객체 + 역호환 지원 완료 — LGTM

이전 리뷰에서 제기된 시그니처 불일치 이슈가 해결되었습니다. 기존 2·3인자 호출과 신규 옵션-객체 형태 모두 동작합니다.

🧹 Nitpick comments (8)
src/shared/constants/error-toast.ts (1)

12-18: 리터럴 타입 보존을 위해 as const를 붙여 주세요

하드코딩된 메시지가 변형되지 않도록 리터럴 타입을 고정하면 오타·회귀를 줄일 수 있습니다.

-export const MATCH_PENDING_TOAST_MESSAGES = {
+export const MATCH_PENDING_TOAST_MESSAGES = {
   REQUEST_WAITING: '메이트의 요청을 기다리는 중입니다.',
   APPROVAL_WAITING: {
     single: '메이트의 승인을 기다리는 중입니다.',
     group: '메이트 전원의 승인을 기다리는 중입니다.',
   },
-};
+} as const;

추가로, 호출부에서 타입을 활용하려면(선택) 아래 타입을 외부로 노출하는 것도 고려해주세요:

export type MatchPendingToastMessages = typeof MATCH_PENDING_TOAST_MESSAGES;
src/shared/utils/show-error-toast.tsx (3)

28-39: options 객체가 전달될 때 3번째 인자(showIcon)는 무시됩니다 — 오버로드/디프리케이션 표기 권장

실제 동작은 문제없지만, 혼용 호출에서 혼란을 줄이려면 함수 오버로드로 의도를 명확히 하거나 3번째 인자를 deprecate 주석으로 안내하는 것을 권장합니다.

// 제안: 오버로드 선언(구현부 위)
export type ShowErrorToastOptions = { icon?: boolean; offset?: string };

export function showErrorToast(message: string, offset?: string, showIcon?: boolean): void;
export function showErrorToast(message: string, options?: ShowErrorToastOptions): void;

17-25: 기본 동작 상수 분리/재사용은 👍 — 클래스네임의 강제 우선순위는 유지가 필요한지 확인

Tailwind 강제 우선순위('!')가 디자인 토큰과 충돌할 수 있습니다. 상위에서 커스터마이즈 필요 시 덮어쓰기 어려울 수 있으니 의도된 고정인지 한 번만 확인해주세요.


11-16: offset 검증 로직 완화
rem 전용 검증(isValidRem) 대신 비어 있지 않은 값 검증(isNonEmpty)으로 변경을 권장합니다.

-const isValidRem = (v?: string) => !!v && /^-?\d+(\.\d+)?rem$/i.test(v);
+const isNonEmpty = (v?: string) => typeof v === 'string' && v.trim().length > 0;
@@
-const { icon = true, offset } = opts ?? {};
-const resolvedOffset = isValidRem(offset) ? offset : DEFAULT_OFFSET;
+const { icon = true, offset } = opts ?? {};
+const resolvedOffset = isNonEmpty(offset) ? offset! : DEFAULT_OFFSET;

showErrorToast 호출부에서 px/var/calc/% 단위 사용 여부를 직접 확인해 주세요.

src/pages/match/components/match-tab-pannel.tsx (4)

30-35: 대기 상태 토스트 게이트 로직 적절 — 호출 시 options 형태로 통일 권장

API가 options 객체를 지원하므로 가독성/오타 방지 측면에서 객체 형태로 호출을 통일하면 좋습니다.

-    if (toastMsg) {
-      showErrorToast(toastMsg, '8.6rem', false);
+    if (toastMsg) {
+      showErrorToast(toastMsg, { offset: '8.6rem', icon: false });
       return;
     }

39-46: 상태 전환 실패 시 사용자 피드백이 없습니다

catch 블록에서 로그만 남기면 사용자 입장에선 무응답처럼 보입니다. 간단한 에러 토스트를 추가해 주세요.

   } catch (error) {
-    console.error('매칭 상태 전환 실패', error);
+    showErrorToast('상태 전환에 실패했어요. 잠시 후 다시 시도해 주세요.', { offset: '8.6rem' });
+    console.error('매칭 상태 전환 실패', error);
   }

49-49: 함수 명세 의미 명확화(선택)

isClickable은 실제로는 “내비게이션 가능한 상태인지”를 나타냅니다. isNavigable 같은 네이밍이 의도를 더 잘 드러낼 수 있습니다.


1-1: 파일명 오탈자 정정 제안

파일명이 match-tab-pannel.tsx입니다. panel 철자 통일을 검토해 주세요(리네임 영향 범위 확인 필요).

📜 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 20585b9 and 6a4faac.

📒 Files selected for processing (4)
  • src/pages/match/components/match-tab-pannel.tsx (4 hunks)
  • src/pages/match/utils/match-status.ts (2 hunks)
  • src/shared/constants/error-toast.ts (1 hunks)
  • src/shared/utils/show-error-toast.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/match/utils/match-status.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/pages/match/components/match-tab-pannel.tsx (3)
src/pages/match/utils/match-status.ts (1)
  • getPendingToast (23-35)
src/shared/utils/show-error-toast.tsx (1)
  • showErrorToast (28-40)
src/pages/match/constants/matching.ts (1)
  • CLICKABLE_STATUS_MAP (43-47)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: chromatic
🔇 Additional comments (1)
src/pages/match/components/match-tab-pannel.tsx (1)

64-69: onClick 항상 바인딩 + aria-disabled 병행 접근성 전략 — LGTM

대기 상태 토스트를 위해 클릭을 유지하면서, 비활성화를 aria로 표현하는 방식이 요구사항과 잘 맞습니다.

Copy link

@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)
src/shared/components/card/match-card/components/card-profile-image.tsx (1)

6-15: imgUrl 런타임 string 분기 제거 및 타입 계약 준수 필요 (TS 오류/죽은 분기)

CardProfileProps['imgUrl']는 현재 string[]인데, typeof url === 'string' 체크가 들어가 있어 타입상 절대 참이 될 수 없는 분기입니다. Strict 모드에서는 “조건은 항상 false” 오류로 빌드가 막힐 수 있습니다. 과거 논의/러닝대로 컴포넌트에서는 배열만 가정하고, 문자열→배열 정규화는 매핑 레이어에서 처리해 주세요.

아래처럼 normalizeUrls를 제거하고 단순화하세요.

-const CardProfile = ({ type, imgUrl }: CardProfileProps) => {
-  const normalizeUrls = (url?: CardProfileProps['imgUrl']): string[] => {
-    if (typeof url === 'string') return [url];
-    return url ?? [];
-  };
-
-  const urls = normalizeUrls(imgUrl);
+const CardProfile = ({ type, imgUrl }: CardProfileProps) => {
+  const urls = imgUrl;
   const slotCount = type === 'group' ? 4 : 1;
   const slots = Array.from({ length: slotCount }, (_, i) => urls[i] ?? '');

필요 시 호출부에서 문자열 사용 여부를 점검해 주세요:

#!/bin/bash
# CardProfile imgUrl 전달 형태 점검
rg -nP -C2 '(<CardProfile\b[^>]*imgUrl=)(\{[^}]+\}|\"[^\"]+\")'
🧹 Nitpick comments (5)
src/shared/components/card/match-card/components/card-profile-image.tsx (5)

23-51: React key 안정화 및 사이즈 클래스 중복 제거

  • key로 src를 쓰면 중복 URL에서 충돌/불안정이 생길 수 있습니다. 인덱스 기반으로 고정하세요.
  • 컨테이너(profileVariants)가 이미 사이즈를 갖고 있는데, 이미지에 h-[2.8rem] w-[2.8rem]를 다시 지정하고 있습니다. 이미지엔 w-full h-full object-cover만 주면 충분합니다.
-  <div
-    key={hasSrc ? src : `slot-${i}`}
+  <div
+    key={`slot-${i}`}
     className={cn(
       'flex items-center justify-center overflow-hidden rounded-full',
       profileVariants({ type }),
       zIndexClasses[i],
     )}
   >
     {hasSrc ? (
       <img
         src={src}
-        alt=""
+        alt=""
         loading="lazy"
         decoding="async"
-        className="h-[2.8rem] w-[2.8rem] rounded-full object-cover"
+        className="h-full w-full rounded-full object-cover"
       />

45-45: 컬러 토큰 사용 검토

bg-gray-400은 디자인 토큰을 우회합니다. 테마 변수/토큰(text-…, bg-…, 혹은 CSS var)로 치환해 일관성을 유지해 주세요.


53-70: Icon API 사용 일관화 및 이미지 크기 지정 통일

  • 동일 컴포넌트에서 sizewidth/height 혼용은 유지보수 부담입니다. 한쪽으로 통일하세요(권장: size).
  • 싱글 이미지도 w-full h-full object-cover로 맞추면 컨테이너 사이즈만으로 일관 렌더링됩니다.
-  className={cn('overflow-hidden rounded-full', profileVariants({ type }))}
+  className={cn('overflow-hidden rounded-full', profileVariants({ type }))}
 ...
-    <img
+    <img
       src={src}
       alt=""
       loading="lazy"
       decoding="async"
-      className="overflow-hidden rounded-full object-cover"
+      className="h-full w-full rounded-full object-cover"
     />
   ) : (
-    <Icon width={6} height={6} name="profile" className="overflow-hidden rounded-full" />
+    <Icon size={6} name="profile" className="overflow-hidden rounded-full" />
   )}

38-43: 접근성: alt 전달 경로 확보(선택)

프로필 이미지는 의미 있는 콘텐츠일 가능성이 높습니다. 가능하면 alt를 Prop으로 받아 사용자명 등 의미를 전달하세요. 타입/호출부 변경이 필요합니다.

타입 제안(다른 파일):

// src/shared/components/card/match-card/types/card.ts
export interface CardProfileProps {
  type: 'single' | 'group' | 'detailed' | 'user';
  imgUrl: string[];
  alt?: string; // 새로 추가
}

사용:

- alt=""
+ alt={alt ?? ''}

Also applies to: 60-65


76-79: 래퍼/자식 중복 클래스 정리(미세)

래퍼와 자식 모두에 overflow-hidden rounded-full가 중복 설정되어 있습니다. 한쪽으로만 유지해도 동작합니다.

📜 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 6a4faac and 599e885.

📒 Files selected for processing (2)
  • src/shared/components/card/match-card/components/card-header.tsx (4 hunks)
  • src/shared/components/card/match-card/components/card-profile-image.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/shared/components/card/match-card/components/card-header.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-01T11:48:49.279Z
Learnt from: bongtta
PR: MATEBALL/MATEBALL-CLIENT#332
File: src/shared/components/card/match-card/components/card-profile-image.tsx:16-18
Timestamp: 2025-09-01T11:48:49.279Z
Learning: In CardProfile component for match cards, prefer fixing imgUrl type to string[] and normalizing data at the mapping layer rather than using runtime Array.isArray checks in the component.

Applied to files:

  • src/shared/components/card/match-card/components/card-profile-image.tsx
🧬 Code graph analysis (1)
src/shared/components/card/match-card/components/card-profile-image.tsx (3)
src/shared/components/card/match-card/types/card.ts (1)
  • CardProfileProps (78-81)
src/shared/libs/cn.ts (1)
  • cn (4-6)
src/shared/components/card/match-card/styles/card-variants.ts (1)
  • profileVariants (22-34)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: chromatic

Comment on lines +9 to +13
const normalizeChipKey = (v?: string) => (v ?? '').replace(/\s/g, '');

const isChipColor = (k: string): k is ChipColor =>
Object.prototype.hasOwnProperty.call(chipVariantOptions.bgColor, k);

Copy link
Contributor

Choose a reason for hiding this comment

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

마찬가지로 utils로 이동하면 좋을 것 같아요!

Copy link

@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

♻️ Duplicate comments (2)
src/pages/match/hooks/mapMatchData.ts (1)

15-16: imgUrl 매핑 시 빈 값 필터링 권장

빈 문자열이 들어오면 <img src=""> 요청이 발생할 수 있습니다. 공백/중복 제거 포함한 정규화 유틸을 활용해 매핑 단계에서 정리하세요.

-      imgUrl: [mate.imgUrl],
+      // 빈 문자열/중복 제거를 포함한 정규화
+      imgUrl: (mate.imgUrl ? [mate.imgUrl] : []).filter((u) => u.trim().length > 0),

또는 공유 유틸 사용:

+import { normalizeUrls } from '@components/card/match-card/utils/normalize-urls';
...
-      imgUrl: [mate.imgUrl],
+      imgUrl: normalizeUrls(mate.imgUrl),
src/shared/components/card/match-card/components/card-header.tsx (1)

30-49: 중복 제거와 왕관 오버레이 구조 LGTM

renderProfile로 헤더 변형 별 중복을 잘 걷어냈고, isCreated 조건부 아이콘 오버레이도 접근성 속성(aria-hidden) 처리 적절합니다.

🧹 Nitpick comments (10)
src/shared/components/card/match-card/utils/normalize-urls.ts (1)

3-6: URL 정규화 보강(트림/중복 제거/널 허용) 제안

빈 문자열과 공백-only 값, 중복 URL을 제거하면 이미지 로딩 안정성이 올라갑니다. null도 허용해두면 호출부가 단순해집니다.

-export type ImageUrlInput = string | string[] | undefined;
+export type ImageUrlInput = string | string[] | null | undefined;

 export function normalizeUrls(url: ImageUrlInput): string[] {
-  if (typeof url === 'string') return [url];
-  return url ?? [];
+  const arr = (typeof url === 'string' ? [url] : url) ?? [];
+  const seen = new Set<string>();
+  for (const u of arr) {
+    const s = (u ?? '').trim();
+    if (s && !seen.has(s)) seen.add(s);
+  }
+  return [...seen];
 }
src/pages/match/utils/match-status.ts (1)

26-38: type 미지정 시 오토-싱글 메시지 노출 방지

'승인 대기 중'에서 typeundefined면 싱글 메시지가 노출됩니다. 잘못된 호출을 조기에 막도록 가드를 추가하는 편이 안전합니다. (상태/문구 상수화는 추후 리팩토링으로 별도 제안)

 export const getPendingToast = (
   status?: string,
   type?: MatchableCardProps['type'],
 ): string | '' => {
   if (!status) return '';
   if (status === '요청 대기 중') return MATCH_PENDING_TOAST_MESSAGES.REQUEST_WAITING;
-  if (status === '승인 대기 중') {
+  if (status === '승인 대기 중') {
+    if (!type) return '';
     return type === 'group'
       ? MATCH_PENDING_TOAST_MESSAGES.APPROVAL_WAITING.group
       : MATCH_PENDING_TOAST_MESSAGES.APPROVAL_WAITING.single;
   }
   return '';
 };
src/shared/components/card/constants/MATCH.ts (1)

1-10: 컴포넌트로부터 타입 의존 제거 + 상수 중복 제거

constants 레이어가 컴포넌트에 의존하면 순환/레이어링 리스크가 있습니다. 로컬 리터럴 유니온으로 대체하고, GROUP_MAXPROFILE_SLOT_COUNT.group에서 파생해 중복을 제거하는 것을 권장합니다.

-import type { ProfileType } from '@components/card/match-card/components/card-profile-image';
-
-export const GROUP_MAX = 4;
-
-export const PROFILE_SLOT_COUNT: Record<ProfileType, number> = {
+type ProfileType = 'group' | 'single' | 'detailed' | 'user';
+
+export const PROFILE_SLOT_COUNT: Record<ProfileType, number> = {
   group: 4,
   single: 1,
   detailed: 1,
   user: 1,
 };
+
+export const GROUP_MAX = PROFILE_SLOT_COUNT.group;
src/shared/components/card/match-card/components/card-header.tsx (1)

15-28: 왕관 위치/크기 매직 넘버 상수화 제안

'1.2rem/1.6rem', 좌우 오프셋 값들이 분산되어 유지보수 비용이 생깁니다. 타입별 사양을 상수 맵으로 끌어올리거나 theme 토큰으로 관리해 주세요.

-const getCrownSpec = (t: CardProps['type']) => {
-  if (t === 'group') {
-    return { box: 'h-[1.2rem] w-[1.2rem]', pos: 'left-[1.4rem] -bottom-[0.2rem]', size: 1.2 };
-  }
-  return { box: 'h-[1.6rem] w-[1.6rem]', pos: 'right-[0.3rem] -bottom-[0.2rem]', size: 1.6 };
-};
+const CROWN_SPECS: Record<CardProps['type'], { box: string; pos: string; size: number }> = {
+  group: { box: 'h-[1.2rem] w-[1.2rem]', pos: 'left-[1.4rem] -bottom-[0.2rem]', size: 1.2 },
+  single: { box: 'h-[1.6rem] w-[1.6rem]', pos: 'right-[0.3rem] -bottom-[0.2rem]', size: 1.6 },
+  detailed:{ box: 'h-[1.6rem] w-[1.6rem]', pos: 'right-[0.3rem] -bottom-[0.2rem]', size: 1.6 },
+  user:   { box: 'h-[1.6rem] w-[1.6rem]', pos: 'right-[0.3rem] -bottom-[0.2rem]', size: 1.6 },
+};
+const getCrownSpec = (t: CardProps['type']) => CROWN_SPECS[t];
src/pages/match/components/match-tab-pannel.tsx (3)

67-72: aria-disabled는 실제 동작과 불일치 → 제거 권장

aria-disabled=true는 “비대화형” 의미인데, 현재 클릭 시 토스트가 뜹니다. 스크린리더에 혼선이 생깁니다. 비활성화 표현은 커서/스타일로만 처리하고 aria-disabled는 제거하세요.

-            aria-disabled={!isClickable(card.status)}
+            /* 클릭 시 토스트 노출: 보조기술 혼선을 막기 위해 aria-disabled는 사용하지 않음 */

45-49: 중복 클릭 방지 및 실패 피드백 보강

승인 완료 시 연속 클릭으로 중복 mutate가 발생할 수 있습니다. 진행 중 가드와 에러 토스트를 추가해 주세요.

   try {
-      if (card.status === '승인 완료') {
-        await patchStageMutation.mutateAsync(card.id);
-      }
+      if (patchStageMutation.isPending) return;
+      if (card.status === '승인 완료') {
+        await patchStageMutation.mutateAsync(card.id);
+      }
       navigate(`${ROUTES.RESULT(card.id.toString())}?type=${query}&cardtype=${card.type}`);
   } catch (error) {
-      console.error('매칭 상태 전환 실패', error);
+      showErrorToast('매칭 상태 전환에 실패했어요. 잠시 후 다시 시도해 주세요.', '8.6rem', false);
   }

35-39: 오프셋 매직 넘버 상수화

'8.6rem'은 디자인 토큰/상수로 추출하면 재사용과 변경 추적이 수월합니다.

-      showErrorToast(toastMsg, '8.6rem', false);
+      // e.g. src/shared/constants/toast.ts
+      // export const TOAST_OFFSET_MATCH = '8.6rem';
+      showErrorToast(toastMsg, TOAST_OFFSET_MATCH, false);
src/shared/components/card/match-card/components/card-profile-image.tsx (3)

10-14: 타입 계약이 string[]이면 컴포넌트 내 정규화 불필요

CardProfileProps.imgUrl이 이미 string[]이므로 normalizeUrls 호출은 중복입니다. 매핑 레이어에서 정규화하고 여기선 바로 사용하세요. (저장된 learning 반영)

-import { normalizeUrls } from '@components/card/match-card/utils/normalize-urls';
...
-  const urls = normalizeUrls(imgUrl);
+  const urls = imgUrl; // 이미 string[] 계약

22-49: React key 안정성 및 중복 스타일 제거

  • key에 src를 쓰면 동일 URL 반복 시 중복 key 경고가 날 수 있습니다. index 기반으로 단순화하세요.
  • 그룹 이미지/플레이스홀더에 h/w 클래스가 컨테이너와 중복됩니다. 자식에는 h-full w-full로 일관화하는 편이 안전합니다.
-      <div
-        key={hasSrc ? src : `slot-${i}`}
+      <div
+        key={i}
         className={cn(
           'flex items-center justify-center overflow-hidden rounded-full',
           profileVariants({ type }),
           zIndexClasses[i],
         )}
       >
-        {hasSrc ? (
+        {hasSrc ? (
           <img
             src={src}
             alt=""
             loading="lazy"
-            className="h-[2.8rem] w-[2.8rem] rounded-full object-cover"
+            className="h-full w-full rounded-full object-cover"
           />
         ) : isEmptyTail ? (
-          <div className="h-[2.8rem] w-[2.8rem] rounded-full bg-gray-400" />
+          <div className="h-full w-full rounded-full bg-gray-400" />
         ) : (
           <Icon size={2.8} name="profile" className="rounded-full" />
         )}
       </div>

51-69: 단건 이미지의 alt/decoding 재고

프로필이 정보성을 가진 경우 alt=""는 비권장입니다. 상위에서 닉네임을 alt로 내려받도록 확장하는 것을 고려해 주세요. decoding="async"는 큰 이득이 적어 제거 가능.

-const CardProfile = ({ type, imgUrl }: CardProfileProps) => {
+const CardProfile = ({ type, imgUrl, alt }: CardProfileProps & { alt?: string }) => {
...
-      {src ? (
+      {src ? (
         <img
           src={src}
-          alt=""
-          loading="lazy"
-          decoding="async"
+          alt={alt ?? ''}
+          loading="lazy"
           className="overflow-hidden rounded-full object-cover"
         />
📜 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 599e885 and f244bf0.

📒 Files selected for processing (7)
  • src/pages/match/components/match-tab-pannel.tsx (3 hunks)
  • src/pages/match/hooks/mapMatchData.ts (1 hunks)
  • src/pages/match/utils/match-status.ts (2 hunks)
  • src/shared/components/card/constants/MATCH.ts (1 hunks)
  • src/shared/components/card/match-card/components/card-header.tsx (5 hunks)
  • src/shared/components/card/match-card/components/card-profile-image.tsx (1 hunks)
  • src/shared/components/card/match-card/utils/normalize-urls.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-01T11:48:49.311Z
Learnt from: bongtta
PR: MATEBALL/MATEBALL-CLIENT#332
File: src/shared/components/card/match-card/components/card-profile-image.tsx:16-18
Timestamp: 2025-09-01T11:48:49.311Z
Learning: In CardProfile component for match cards, prefer fixing imgUrl type to string[] and normalizing data at the mapping layer rather than using runtime Array.isArray checks in the component.

Applied to files:

  • src/shared/components/card/constants/MATCH.ts
  • src/shared/components/card/match-card/utils/normalize-urls.ts
  • src/pages/match/hooks/mapMatchData.ts
  • src/shared/components/card/match-card/components/card-header.tsx
  • src/shared/components/card/match-card/components/card-profile-image.tsx
🧬 Code graph analysis (6)
src/shared/components/card/constants/MATCH.ts (1)
src/shared/components/card/match-card/components/card-profile-image.tsx (1)
  • ProfileType (8-8)
src/pages/match/hooks/mapMatchData.ts (3)
src/shared/types/match-types.ts (3)
  • singleMatchMate (30-39)
  • singleMatchMate (65-73)
  • getGroupMatchMate (197-208)
src/shared/components/card/match-card/types/card.ts (2)
  • SingleCardProps (28-39)
  • GroupCardProps (41-47)
src/pages/match/utils/match-status.ts (2)
  • normalizeChipKey (42-42)
  • isChipColor (44-45)
src/shared/components/card/match-card/components/card-header.tsx (3)
src/shared/components/card/match-card/types/card.ts (1)
  • CardProps (76-76)
src/shared/routes/routes-config.ts (1)
  • ROUTES (1-22)
src/shared/libs/cn.ts (1)
  • cn (4-6)
src/pages/match/utils/match-status.ts (4)
src/shared/components/card/match-card/types/card.ts (3)
  • SingleCardProps (28-39)
  • GroupCardProps (41-47)
  • ChipColor (8-8)
src/shared/constants/error-toast.ts (1)
  • MATCH_PENDING_TOAST_MESSAGES (12-18)
src/pages/match/constants/matching.ts (1)
  • CLICKABLE_STATUS_MAP (43-47)
src/shared/components/chip/styles/chip-variants.ts (1)
  • chipVariantOptions (3-36)
src/shared/components/card/match-card/components/card-profile-image.tsx (5)
src/shared/components/card/match-card/types/card.ts (1)
  • CardProfileProps (78-81)
src/shared/components/card/match-card/utils/normalize-urls.ts (1)
  • normalizeUrls (3-6)
src/shared/components/card/constants/MATCH.ts (1)
  • PROFILE_SLOT_COUNT (5-10)
src/shared/libs/cn.ts (1)
  • cn (4-6)
src/shared/components/card/match-card/styles/card-variants.ts (1)
  • profileVariants (22-34)
src/pages/match/components/match-tab-pannel.tsx (2)
src/pages/match/utils/match-status.ts (2)
  • getPendingToast (26-38)
  • isClickable (40-40)
src/shared/utils/show-error-toast.tsx (1)
  • showErrorToast (28-40)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: chromatic
🔇 Additional comments (3)
src/pages/match/utils/match-status.ts (2)

7-12: ‘승인 완료’의 탭 분류 확인 필요

현재 statusToCategory('승인 완료')가 ‘대기 중’으로 분류됩니다. UX 관점에서 ‘완료’ 탭에 포함되어야 한다면 아래처럼 분기 보강이 필요합니다. 의도 확인 부탁드립니다.

-  if (status.includes('매칭 완료')) return '완료';
+  if (status.includes('매칭 완료') || status.includes('승인 완료')) return '완료';

1-5: ChipColor import 경로 검증 완료 해당 경로(@components/chip/chip-list)에서 ChipColor를 정상 export하고 있어 추가 변경 불필요합니다.

src/pages/match/components/match-tab-pannel.tsx (1)

35-39: 대기 상태 토스트 게이팅 흐름 적절

펜딩 상태에서 토스트 노출 후 조기 return으로 네비/뮤테이션 차단하는 로직 명확합니다.

@MATEBALL MATEBALL deleted a comment from coderabbitai bot Sep 2, 2025
@Dubabbi Dubabbi merged commit cb3b899 into develop Sep 2, 2025
6 of 7 checks passed
@Dubabbi Dubabbi deleted the feat/#316/match-status branch September 4, 2025 15:38
@coderabbitai coderabbitai bot mentioned this pull request Sep 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat new feature 소은

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 매칭 현황 페이지를 수정합니다.

3 participants