Conversation
|
Caution Review failedThe pull request is closed. Walkthrough카드 클릭 시 대기 상태 토스트를 우선 검사해 표시하면 네비게이션을 중단하고, 승인 완료 시 patch 호출 후 결과 페이지로 이동합니다. 카드 생성자(isCreated)로 왕관 오버레이를 추가하고, 이미지 슬롯화·칩 정규화·탭 스타일과 토스트 API·여러 mock 파일 및 useMate 훅이 제거되었습니다. Changes
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~55 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related PRs
Suggested reviewers
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (2)
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
|
MATEBALL-STORYBOOK |
There was a problem hiding this comment.
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.
⛔ Files ignored due to path filters (1)
src/shared/assets/svgs/crown.svgis 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 추가 OKUI 크라운 표기를 위한 플래그 추가가 명확합니다. BE 스키마 반영 여부만 확인 부탁드립니다.
src/shared/utils/show-error-toast.tsx (1)
4-7: 옵션 객체 도입 좋습니다아이콘/오프셋을 옵션으로 통합한 점 깔끔합니다.
src/shared/components/tab/tab/styles/tab-style.ts (1)
8-11: 키 네이밍 정리(home): OKborderThickness→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)도 적절합니다.
Deploying mateball-client with
|
| 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 |
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
src/shared/utils/show-error-toast.tsx (1)
18-21: offsetBottom 옵션 객체 호환 누락 — 레그레션 위험
isShowToastOptions는offsetBottom키를 감지하지만, 실제 분기(라인 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 지원)모바일 환경 대응을 위해
isValidLen에dvh|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.
📒 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: 오버로드 시그니처 도입 좋습니다문자열/옵션 객체/미지정 세 케이스를 정확히 캡슐화했습니다. 이후 호출부 변경 비용을 줄일 수 있습니다.
There was a problem hiding this comment.
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.
📒 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을 상속하므로, 매핑 로직에 문제 없습니다.
src/shared/components/card/match-card/components/card-profile-image.tsx
Outdated
Show resolved
Hide resolved
| const normalizeChipKey = (v?: string) => (v ?? '').replace(/\s/g, ''); | ||
|
|
||
| const isChipColor = (k: string): k is ChipColor => | ||
| Object.prototype.hasOwnProperty.call(chipVariantOptions.bgColor, k); | ||
|
|
There was a problem hiding this comment.
api 응답에서 직관 스타일에 띄어쓰기가 포함되어 '열정 응원러'처럼 와서 그동안 스타일 칩이 나타나지 않는 거였어요! 그래서 이렇게 띄어쓰기에도 대응할 수 있도록 추가했습니다.
There was a problem hiding this comment.
마찬가지로 utils로 이동하면 좋을 것 같아요!
| export const getPendingToast = ( | ||
| status?: string, | ||
| type?: MatchableCardProps['type'], | ||
| ): string | '' => { | ||
| if (!status) return ''; | ||
| if (status === '요청 대기 중') return '메이트의 요청을 기다리는 중입니다.'; | ||
| if (status === '승인 대기 중') { | ||
| return type === 'group' | ||
| ? '메이트 전원의 승인을 기다리는 중입니다.' | ||
| : '메이트의 승인을 기다리는 중입니다.'; | ||
| } | ||
| return ''; | ||
| }; |
There was a problem hiding this comment.
요청 상태에 따라 토스트 메시지를 다르게 보여주는 목적입니다!
There was a problem hiding this comment.
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.
📒 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.
There was a problem hiding this comment.
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.
📒 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) 중 하나로 확인되었습니다.
There was a problem hiding this comment.
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.
📒 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-[...]처럼!을 유틸리티 앞에 두는 것이 정석입니다.
bongtta
left a comment
There was a problem hiding this comment.
수고하셨습니당 ㅎㅎ
리뷰 확인부탁드려욥,, 이것까지 머지되어야 남은 작업 가능할 것 같네여 ㅜㅜ
src/shared/components/card/match-card/components/card-profile-image.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
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.
📒 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로 표현하는 방식이 요구사항과 잘 맞습니다.
There was a problem hiding this comment.
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 사용 일관화 및 이미지 크기 지정 통일
- 동일 컴포넌트에서
size와width/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.
📒 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
| const normalizeChipKey = (v?: string) => (v ?? '').replace(/\s/g, ''); | ||
|
|
||
| const isChipColor = (k: string): k is ChipColor => | ||
| Object.prototype.hasOwnProperty.call(chipVariantOptions.bgColor, k); | ||
|
|
There was a problem hiding this comment.
마찬가지로 utils로 이동하면 좋을 것 같아요!
src/shared/components/card/match-card/components/card-header.tsx
Outdated
Show resolved
Hide resolved
src/shared/components/card/match-card/components/card-header.tsx
Outdated
Show resolved
Hide resolved
src/shared/components/card/match-card/components/card-profile-image.tsx
Outdated
Show resolved
Hide resolved
src/shared/components/card/match-card/components/card-profile-image.tsx
Outdated
Show resolved
Hide resolved
src/shared/components/card/match-card/components/card-profile-image.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
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: 중복 제거와 왕관 오버레이 구조 LGTMrenderProfile로 헤더 변형 별 중복을 잘 걷어냈고, 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 미지정 시 오토-싱글 메시지 노출 방지
'승인 대기 중'에서type이undefined면 싱글 메시지가 노출됩니다. 잘못된 호출을 조기에 막도록 가드를 추가하는 편이 안전합니다. (상태/문구 상수화는 추후 리팩토링으로 별도 제안)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_MAX는PROFILE_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.
📒 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.tssrc/shared/components/card/match-card/utils/normalize-urls.tssrc/pages/match/hooks/mapMatchData.tssrc/shared/components/card/match-card/components/card-header.tsxsrc/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으로 네비/뮤테이션 차단하는 로직 명확합니다.
#️⃣ Related Issue
Closes #316
☀️ New-insight
새로운 디자인에서 토스트를 아이콘 없이 사용하는 게 필요해서 토스트 관련 코드를 수정했습니다.
싱글 카드에서 직관 스타일 칩이 안 나타나는 문제와 일대일 매칭 응답이 보이지 않는 오류를 해결했습니다.
또 프로필 이미지를 api 응답에서 가져오는 것으로 수정했습니다!
💎 PR Point
v2로 전환하고 응답 스키마에isCreated필드를 반영했습니다.API / 타입
v2엔드포인트로 변경.isCreated필드 반영UI/UX
대기 상태 클릭 토스트 (
MatchTabPanel)요청 대기 중(1:1/그룹 공통) → “메이트의 요청을 기다리는 중입니다.”승인 대기 중(1:1) → “메이트의 승인을 기다리는 중입니다.”승인 대기 중(그룹) → “메이트 전원의 승인을 기다리는 중입니다.”return)onClick는 항상 바인딩, 커서만 상태에 따라 변경(cursor-pointer)카드 헤더 왕관 표시 (
CardHeader)text-owner, z-index--z-card-owner를theme.css에 추가하여 사용매치 탭 스타일
📸 Screenshot
default.mp4
Summary by CodeRabbit
New Features
Style
Bug Fixes / Accessibility
Chores