fix: SP2 홈 화면 관련 바텀시트, 프로필 카드, 쿼리 등등 수정#420
Conversation
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 15 minutes and 55 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughGameCard의 내부 라우팅을 제거하고 클릭을 상위로 위임하는 콜백 기반으로 재구성했습니다. 홈/게임 페이지에서 매칭 조회 후 MatchingCtaBottomSheet로 매칭 생성(Create Match) 흐름을 구현하고 API 엔드포인트 및 응답 처리 로직을 v3로 업데이트했습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User as 사용자
participant Home as 홈페이지
participant GameList as GameListSection
participant GameCard as GameCard
participant API as Match API
participant BottomSheet as MatchingCtaBottomSheet
participant Result as 결과페이지
User->>Home: 날짜 선택
User->>GameList: 게임 카드 클릭
GameList->>GameCard: onGameClick(game)
GameCard->>Home: onGameClick 콜백 전달
Home->>API: GAME_MATCH_LIST(game.id)
API-->>Home: 매칭 목록 응답
alt 매칭 없음
Home->>BottomSheet: MatchingCtaBottomSheet 열기 (gameSchedule)
User->>BottomSheet: 매칭 타입 선택 및 제출
BottomSheet->>Home: onSubmit(type)
Home->>API: CREATE_MATCH(matchType)
API-->>Home: 생성된 매칭 ID
Home->>Result: 결과 페이지로 네비게이션 (type=success&mode=...)
else 매칭 존재
Home->>Result: 기존 매칭으로 이동 (게임 상세)
end
Result-->>User: 생성/상세 결과 표시
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
MATEBALL-STORYBOOK |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (4)
src/pages/match/components/mate.tsx (1)
61-62: 변수명에 값의 의미를 더 드러내 주세요.
leader만 보면 역할이나 객체처럼 읽히는데 실제 값은 닉네임 문자열입니다.leaderNickname처럼 풀어 두면 아래MateHeader전달부까지 맥락이 더 분명해지고, TODO도 훨씬 구체적으로 정리됩니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/match/components/mate.tsx` around lines 61 - 62, Rename the ambiguous variable leader to leaderNickname (and update the TODO comment) so its string nature is explicit; update every usage of leader across this component (including the prop passed into MateHeader and any other places referencing leader) to use leaderNickname, and ensure any TypeScript types or prop names remain consistent or are updated if necessary to reflect the new name.src/pages/match/create/components/match-guide-section.tsx (1)
2-4: 제목 상수명을 생성 상태에 맞춰 통일하세요.현재 이 컴포넌트는
MATCHING_GUIDE_MESSAGE_TITLE(제목)과MATCHING_GUIDE_CREATED_DESCRIPTION(설명)을 함께 사용하고 있습니다. 생성 완료 상태 전용 제목 상수(MATCHING_GUIDE_CREATED_TITLE)는 존재하지 않으며, 같은 패턴이matching-created-view.tsx에도 반복되고 있습니다.제목의 텍스트 내용("맞춤 매칭이 생성되었어요!")은 생성 상태에 맞지만, 상수 이름이 "MESSAGE"인 반면 설명은 "CREATED"로 네이밍 규칙이 일관되지 않습니다. 코드 가독성과 유지보수성을 위해:
MATCHING_GUIDE_MESSAGE_TITLE을MATCHING_GUIDE_CREATED_TITLE로 이름 변경하거나- 설명(
MATCHING_GUIDE_CREATED_DESCRIPTION)도 "MESSAGE" 패턴으로 통일하세요.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/match/create/components/match-guide-section.tsx` around lines 2 - 4, 현재 컴포넌트가 MATCHING_GUIDE_MESSAGE_TITLE(제목)과 MATCHING_GUIDE_CREATED_DESCRIPTION(설명)을 혼용해 네이밍이 일관되지 않습니다; 수정 방법은 하나를 선택해 적용하세요: (1) constants 파일에서 MATCHING_GUIDE_MESSAGE_TITLE을 MATCHING_GUIDE_CREATED_TITLE로 리네임하고 해당 상수를 이 파일의 import 및 모든 사용처(예: matching-created-view.tsx)에서 교체하거나, (2) 반대로 MATCHING_GUIDE_CREATED_DESCRIPTION을 MATCHING_GUIDE_MESSAGE_DESCRIPTION으로 리네임해 제목 네이밍(MESSAGE 패턴)에 맞추고 모든 사용처를 함께 교체합니다; 또한 constants 모듈(export)과 모든 참조(import 식별자 MATCHING_GUIDE_MESSAGE_TITLE, MATCHING_GUIDE_CREATED_DESCRIPTION)를 일관되게 업데이트하세요.src/shared/apis/match/match-queries.ts (1)
41-61: 응답 언래핑 분기는 공통 헬퍼로 모아두는 편이 안전합니다.같은
status/message/data체크가 이 파일 전체에 반복되고, 같은 패턴이src/shared/apis/match/match-mutations.ts와src/shared/apis/user/user-queries.ts에도 복사돼 있습니다. 한 군데라도 fallback/throw 기준이 어긋나기 시작하면 endpoint별 동작이 쉽게 갈라져서, 지금 단계에서 공통 유틸로 묶어두는 편이 유지보수에 유리합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/shared/apis/match/match-queries.ts` around lines 41 - 61, Extract the repeated "status/message/data" unwrap logic into a shared helper (e.g., unwrapApiResponse<T>(res: T | ApiResponse<T>)) and replace the inline branch inside queryFn in match-queries.ts (the block that checks typeof res === 'object' && 'status' in res && 'message' in res && 'data' in res and throws when !res.data) with a call to that helper so it consistently returns the inner data or throws; then reuse that same helper from other files that currently duplicate the pattern (match-mutations.ts and user-queries.ts) to ensure uniform fallback/throw behavior across all endpoints that call get<...>(...) and expect getSingleMatchResultResponse or similar payloads.src/pages/game/game.tsx (1)
58-85: 매칭 생성 분기 로직은 공용 helper로 묶는 편이 안전합니다.
matchType/queryType/gaMatchType계산,gaEvent('match_create_click'), 성공 후 결과 페이지 라우팅이src/pages/home/home.tsx의 동일 흐름과 사실상 복제돼 있습니다. 이 로직은 이벤트명이나mode쿼리가 조금만 어긋나도 화면별 동작이 갈라지기 쉬워서 지금 시점에 공용 helper/hook으로 정리해 두는 편이 유지보수에 유리합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/game/game.tsx` around lines 58 - 85, The matching creation logic in handleMatchingCtaSubmit (calculating matchType/queryType/gaMatchType, calling gaEvent('match_create_click'), invoking createMatchMutation.mutate, closing sheet and navigating to ROUTES.RESULT) is duplicated with src/pages/home/home.tsx; extract it into a shared helper or hook (e.g., useCreateMatch or createMatchHelper) that accepts the TabType (or explicit role/mode), parsedGameId and createMatchMutation instance, computes the three type values (using TAB_TYPES), fires gaEvent, calls createMatchMutation.mutate with the same onSuccess behavior (setIsMatchingCtaBottomSheetOpen false/close callback should be injectable), and returns a single function to be invoked from both pages to ensure consistent event name, query param `mode`, and routing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/pages/game/game.tsx`:
- Around line 35-56: The click handler opens the matching bottom sheet even when
gameMatchData is not yet loaded; update handleCreateMatchClick to first guard
that gameMatchData exists (or that data is loaded) before using hasCreatedMatch
— if gameMatchData is falsy, early-return or show a loading/error state instead
of calling setIsMatchingCtaBottomSheetOpen; keep the existing
duplicate-prevention logic using hasCreatedMatch and showErrorToast
(HAS_DONE_TOAST_MESSAGE) when data is present.
In `@src/pages/home/components/top-section.tsx`:
- Around line 5-10: Banner shows "명" alone when data is undefined; update the
TopSection JSX that calls useQuery(userQueries.USER_COUNT()) so the count span
renders a safe fallback or the entire sentence only when data.userCnt exists.
Specifically, in the component (top-section.tsx) replace the inline
{data?.userCnt} with a conditional render or placeholder (e.g., data?.userCnt ??
'...') or wrap the whole sentence in a check like data?.userCnt ? <p>현재
<span>{data.userCnt}명</span>이 메이트를 찾고 있어요!</p> : <p>현재 메이트를 찾는 중입니다...</p> so
the UI doesn't show "명" alone.
In `@src/pages/home/home.tsx`:
- Around line 108-121: handleGameClick is vulnerable to race conditions and
unhandled rejections because it immediately sets selectedGame and fires
queryClient.fetchQuery(matchQueries.GAME_MATCH_LIST(...)) without in-flight
protection; update it to track the latest click (e.g., a ref/nonce like
latestClickedGameId or an AbortController token) before calling fetchQuery,
cancel or ignore any earlier responses by comparing the token when the promise
resolves, and wrap the fetchQuery call in try/catch to swallow/log errors so
rejections don't escape the handler; only call setIsMatchingCtaBottomSheetOpen
or navigate when the response token matches the latestClickedGameId and the
component is still mounted.
In `@src/pages/result/components/matching-created-view.tsx`:
- Around line 25-26: The analytics payload currently hardcodes match_type:
'group' in the chat_enter_click event (and similarly at the block around lines
52-55); update the payload construction to branch on the existing
isGroupMatching variable (derived from mode) so match_type is 'group' when
isGroupMatching is true and '1to1' (or the correct 1:1 identifier used
elsewhere) when false; ensure both the chat_enter_click payload and the other
analytics calls referenced (the block around where isGroupMatching is used) use
this conditional so 1:1 results are recorded correctly.
In `@src/shared/apis/match/match-queries.ts`:
- Around line 228-231: MATCH_MEMBERS and MATCH_MEMBERS_DETAIL are using the same
React Query key (MATCH_KEY.MEMBERS(matchId)) causing cache collisions; create a
distinct key MATCH_KEY.MEMBERS_DETAIL(matchId) and update the query definition
for MATCH_MEMBERS_DETAIL to use that new key instead of
MATCH_KEY.MEMBERS(matchId) so the two queryFns (MATCH_MEMBERS returning {
leader, results } and MATCH_MEMBERS_DETAIL returning { results }) have separate
cache entries; ensure any places that invalidate or refetch members-detail use
the new MATCH_KEY.MEMBERS_DETAIL symbol.
In `@src/shared/constants/api.ts`:
- Line 21: The UI currently uses the GET_USER_COUNT endpoint (constant
GET_USER_COUNT) whose type in src/shared/types/user-types.ts documents it as
total users, but top-section.tsx displays it as "currently looking" (userCnt),
causing mismatch; either (A) change the top-section.tsx copy to reflect "전체 가입자
수" and keep using GET_USER_COUNT/userCnt as-is, or (B) add a new endpoint/metric
for real-time waiting users and wire that into top-section.tsx (create a new
constant, a matching type in src/shared/types/user-types.ts, and replace userCnt
with the new value); update variable names and UI copy to unambiguously match
the chosen metric (e.g., totalUsers vs currentWaitingUsers).
---
Nitpick comments:
In `@src/pages/game/game.tsx`:
- Around line 58-85: The matching creation logic in handleMatchingCtaSubmit
(calculating matchType/queryType/gaMatchType, calling
gaEvent('match_create_click'), invoking createMatchMutation.mutate, closing
sheet and navigating to ROUTES.RESULT) is duplicated with
src/pages/home/home.tsx; extract it into a shared helper or hook (e.g.,
useCreateMatch or createMatchHelper) that accepts the TabType (or explicit
role/mode), parsedGameId and createMatchMutation instance, computes the three
type values (using TAB_TYPES), fires gaEvent, calls createMatchMutation.mutate
with the same onSuccess behavior (setIsMatchingCtaBottomSheetOpen false/close
callback should be injectable), and returns a single function to be invoked from
both pages to ensure consistent event name, query param `mode`, and routing.
In `@src/pages/match/components/mate.tsx`:
- Around line 61-62: Rename the ambiguous variable leader to leaderNickname (and
update the TODO comment) so its string nature is explicit; update every usage of
leader across this component (including the prop passed into MateHeader and any
other places referencing leader) to use leaderNickname, and ensure any
TypeScript types or prop names remain consistent or are updated if necessary to
reflect the new name.
In `@src/pages/match/create/components/match-guide-section.tsx`:
- Around line 2-4: 현재 컴포넌트가 MATCHING_GUIDE_MESSAGE_TITLE(제목)과
MATCHING_GUIDE_CREATED_DESCRIPTION(설명)을 혼용해 네이밍이 일관되지 않습니다; 수정 방법은 하나를 선택해
적용하세요: (1) constants 파일에서 MATCHING_GUIDE_MESSAGE_TITLE을
MATCHING_GUIDE_CREATED_TITLE로 리네임하고 해당 상수를 이 파일의 import 및 모든 사용처(예:
matching-created-view.tsx)에서 교체하거나, (2) 반대로 MATCHING_GUIDE_CREATED_DESCRIPTION을
MATCHING_GUIDE_MESSAGE_DESCRIPTION으로 리네임해 제목 네이밍(MESSAGE 패턴)에 맞추고 모든 사용처를 함께
교체합니다; 또한 constants 모듈(export)과 모든 참조(import 식별자 MATCHING_GUIDE_MESSAGE_TITLE,
MATCHING_GUIDE_CREATED_DESCRIPTION)를 일관되게 업데이트하세요.
In `@src/shared/apis/match/match-queries.ts`:
- Around line 41-61: Extract the repeated "status/message/data" unwrap logic
into a shared helper (e.g., unwrapApiResponse<T>(res: T | ApiResponse<T>)) and
replace the inline branch inside queryFn in match-queries.ts (the block that
checks typeof res === 'object' && 'status' in res && 'message' in res && 'data'
in res and throws when !res.data) with a call to that helper so it consistently
returns the inner data or throws; then reuse that same helper from other files
that currently duplicate the pattern (match-mutations.ts and user-queries.ts) to
ensure uniform fallback/throw behavior across all endpoints that call
get<...>(...) and expect getSingleMatchResultResponse or similar payloads.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: baa82b6d-e6e9-4215-baac-3ebf63380e1a
📒 Files selected for processing (20)
src/pages/game/components/game-card.tsxsrc/pages/game/game.tsxsrc/pages/home/components/game-list-section.tsxsrc/pages/home/components/top-section.tsxsrc/pages/home/home.tsxsrc/pages/match/components/mate.tsxsrc/pages/match/constants/matching.tssrc/pages/match/create/components/match-guide-section.tsxsrc/pages/result/components/matching-created-view.tsxsrc/pages/result/result.tsxsrc/shared/apis/match/match-mutations.tssrc/shared/apis/match/match-queries.tssrc/shared/apis/user/user-queries.tssrc/shared/components/bottom-sheet/bottom-sheet.tsxsrc/shared/components/bottom-sheet/matching-cta/matching-cta-bottom-sheet.tsxsrc/shared/constants/api.tssrc/shared/constants/header.tssrc/shared/constants/query-key.tssrc/shared/types/match-types.tssrc/shared/types/user-types.ts
💤 Files with no reviewable changes (1)
- src/shared/components/bottom-sheet/bottom-sheet.tsx
| const handleGameClick = async (game: GameCardItem) => { | ||
| setSelectedGame(game); | ||
|
|
||
| const gameMatchData = await queryClient.fetchQuery(matchQueries.GAME_MATCH_LIST(game.id)); | ||
|
|
||
| const hasMatchCard = (gameMatchData.result ?? []).length > 0; | ||
|
|
||
| if (!hasMatchCard) { | ||
| setIsMatchingCtaBottomSheetOpen(true); | ||
| return; | ||
| } | ||
|
|
||
| navigate(ROUTES.GAME(dateStr, String(game.id))); | ||
| }; |
There was a problem hiding this comment.
게임 카드 클릭은 in-flight 보호가 필요합니다.
지금은 클릭할 때마다 fetchQuery를 병렬로 날리고 바로 selectedGame을 바꿉니다. 그래서 A 요청이 늦게 돌아오면 B를 선택한 상태에서도 A 결과로 바텀시트가 열릴 수 있고, 요청이 실패하면 fetchQuery(...) rejection도 이벤트 핸들러 밖으로 빠집니다. 클릭 중 상태를 두거나 요청 토큰을 비교해서 마지막 선택에만 후속 동작을 적용해 주세요.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/home/home.tsx` around lines 108 - 121, handleGameClick is
vulnerable to race conditions and unhandled rejections because it immediately
sets selectedGame and fires
queryClient.fetchQuery(matchQueries.GAME_MATCH_LIST(...)) without in-flight
protection; update it to track the latest click (e.g., a ref/nonce like
latestClickedGameId or an AbortController token) before calling fetchQuery,
cancel or ignore any earlier responses by comparing the token when the promise
resolves, and wrap the fetchQuery call in try/catch to swallow/log errors so
rejections don't escape the handler; only call setIsMatchingCtaBottomSheetOpen
or navigate when the response token matches the latestClickedGameId and the
component is still mounted.
Deploying mateball-client with
|
| Latest commit: |
1c2edd2
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://e905a2c8.mateball-client.pages.dev |
| Branch Preview URL: | https://fix--419-fix-home.mateball-client.pages.dev |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/pages/result/components/matching-created-view.tsx (1)
99-105:imgUrl정규화 로직이 방어적으로 구현되어 있습니다.
getSingleMatchResultResponse의imgUrl이string이고getGroupMatchResultResponse는string[]이기 때문에 런타임 정규화가 필요합니다. 현재 구현은 동작하지만, 이 정규화를 쿼리/API 레이어에서 처리하면 컴포넌트가 더 깔끔해집니다.♻️ 쿼리 레이어에서 정규화하는 방안
match-queries.ts의SINGLE_MATCH_RESULT셀렉터에서imgUrl을 배열로 정규화하면, 컴포넌트에서는 항상string[]타입으로 일관되게 사용할 수 있습니다:// match-queries.ts의 SINGLE_MATCH_RESULT select 옵션에서 select: (data) => ({ ...data, imgUrl: Array.isArray(data.imgUrl) ? data.imgUrl : [data.imgUrl], }),이렇게 하면 Line 100을 단순히
imgUrl={createdData.imgUrl}로 변경할 수 있습니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/result/components/matching-created-view.tsx` around lines 99 - 105, Normalize imgUrl in the query layer instead of the component: update the SINGLE_MATCH_RESULT selector in match-queries.ts to map the response to always return imgUrl as string[] (e.g., replace imgUrl with Array.isArray(data.imgUrl) ? data.imgUrl : [data.imgUrl] in the select result), then simplify matching-created-view.tsx to pass imgUrl={createdData.imgUrl} (remove the runtime Array.isArray normalization) so the component always receives string[].
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/pages/result/components/matching-created-view.tsx`:
- Around line 99-105: Normalize imgUrl in the query layer instead of the
component: update the SINGLE_MATCH_RESULT selector in match-queries.ts to map
the response to always return imgUrl as string[] (e.g., replace imgUrl with
Array.isArray(data.imgUrl) ? data.imgUrl : [data.imgUrl] in the select result),
then simplify matching-created-view.tsx to pass imgUrl={createdData.imgUrl}
(remove the runtime Array.isArray normalization) so the component always
receives string[].
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 04572484-aa69-4554-b6bd-5ff913dbba58
📒 Files selected for processing (5)
src/pages/game/game.tsxsrc/pages/result/components/matching-created-view.tsxsrc/shared/apis/match/match-queries.tssrc/shared/apis/user/user-queries.tssrc/shared/constants/query-key.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- src/shared/constants/query-key.ts
- src/shared/apis/match/match-queries.ts
- src/shared/apis/user/user-queries.ts
- src/pages/game/game.tsx
#️⃣ Related Issue
Closes #419
☀️ New-insight
💎 PR Point
➊ 홈 경기 카드 및 매칭 카드 → 매칭 생성 CTA 흐름 연결
메이트 만나기버튼 클릭 시 바로 game 페이지로 이동하지 않고, 해당 경기의 매칭 리스트를 먼저 조회하도록 변경MatchingCtaBottomSheet를 노출하고, 존재할 경우 game페이지로 이동하도록 분기➋ CTA 바텀시트에서 바로 매칭 생성
GameMatchBottomSheet로 한 번 더 이동하던 기존 흐름에서, 요구사항에 맞게매칭 생성하기버튼 클릭 시 바로 생성 mutation을 호출하도록 변경➌ 결과 페이지 ui 수정
type=success일 때 single/group이 서로 다른 컴포넌트로 분기되던 흐름을 정리하고, 동일한 ui를 재사용하도록 구조 조정➍ query / mutation 응답 정규화
status/message/data형태의 응답은 query/mutation 내부에서 data를 벗겨 반환하도록 수정++ 날짜 유지 로직은 우선 주석처리 해놓았고 나중에 수정할 예정입니다... (이거 때문에 자꾸 에러가 나서ㅜ.,ㅜ)
📸 Screenshot
Summary by CodeRabbit
새로운 기능
개선 사항
문구/콘텐츠