Skip to content

fix: SP2 매칭 조건 설정 페이지 수정#402

Open
bongtta wants to merge 22 commits intodevelopfrom
fix/#398/initial-matching-condition
Open

fix: SP2 매칭 조건 설정 페이지 수정#402
bongtta wants to merge 22 commits intodevelopfrom
fix/#398/initial-matching-condition

Conversation

@bongtta
Copy link
Contributor

@bongtta bongtta commented Mar 20, 2026

#️⃣ Related Issue

Closes #398

💎 PR Point

일부 api는 바로 연결하려 했는데 아직 배포 안 된 것들이 있어서 나중에 다른 브랜치에서 작업할게요~

  • 자잘한 텍스트 수정
  • 시즌 평균 직관 수 페이지 제작
  • 매칭 생성 제안 페이지 제작
  • 온보딩 전체 로직 수정

📸 Screenshot

스크린샷 2026-03-20 오후 5 26 19

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 시즌 중 관전 횟수 입력 기능 추가
    • 매칭 생성 완료 후 카드 표시 기능
  • 개선 사항

    • 온보딩 플로우 재구성 및 최적화
    • 그룹 매칭 최대 4인 제한 명시
    • 성별 선호도 대신 평균 관전 횟수 기반으로 변경
    • 날짜 선택 인터페이스 강화
  • 버그 수정

    • 알림 UI 색상 개선

@bongtta bongtta self-assigned this Mar 20, 2026
@bongtta bongtta requested a review from heesunee as a code owner March 20, 2026 08:29
@bongtta bongtta linked an issue Mar 20, 2026 that may be closed by this pull request
@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Mar 20, 2026

Deploying mateball-client with  Cloudflare Pages  Cloudflare Pages

Latest commit: aae2049
Status: ✅  Deploy successful!
Preview URL: https://204c7fd5.mateball-client.pages.dev
Branch Preview URL: https://fix--398-initial-matching-co.mateball-client.pages.dev

View logs

@coderabbitai
Copy link

coderabbitai bot commented Mar 20, 2026

Warning

Rate limit exceeded

@bongtta has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 13 minutes and 6 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0ff62e24-c3ad-4257-a240-895e773c0333

📥 Commits

Reviewing files that changed from the base of the PR and between 6703259 and aae2049.

📒 Files selected for processing (2)
  • src/pages/onboarding/onboarding.tsx
  • src/shared/constants/api.ts

📋 워크스루

온보딩 퍼널 플로우를 리팩토링하여 START와 GENDER 단계를 제거하고 FREQUENCY 및 DATE_SELECT 단계를 추가했습니다. 새로운 완료 카드 컴포넌트를 도입하고, 매칭 조건 API를 genderPreference에서 avgSeason으로 변경하여 관련 타입 정의를 업데이트했습니다.

📊 변경 사항

코호트 / 파일 요약
온보딩 퍼널 및 단계 관리
src/pages/onboarding/onboarding.tsx, src/pages/onboarding/onboarding-group.tsx, src/shared/hooks/use-funnel.tsx
퍼널 아키텍처를 리팩토링하여 Funnel이 currentStep을 명시적으로 받도록 변경했습니다. DATE_SELECT, FREQUENCY 단계 추가, GENDER 단계 제거, 그리고 createdMatch 상태로 완료 단계 처리 방식을 변경했습니다.
온보딩 컴포넌트 추가
src/pages/onboarding/components/complete.tsx, src/pages/onboarding/components/complete-single-card.tsx, src/pages/onboarding/components/complete-group-card.tsx, src/pages/onboarding/components/complete-button-section.tsx, src/pages/onboarding/components/frequency.tsx
새로운 Frequency 입력 컴포넌트, CompleteSingleCard 및 CompleteGroupCard 데이터 표시 컴포넌트, 그리고 CompleteButtonSection 액션 컴포넌트를 추가했습니다. Complete 컴포넌트를 nickname/matchId/type 속성을 받도록 업데이트했습니다.
온보딩 헤더 및 텍스트
src/pages/onboarding/components/matching-type.tsx, src/pages/onboarding/components/sync-support-team.tsx, src/pages/onboarding/components/date-select.tsx, src/pages/onboarding/components/start.tsx
헤더 텍스트와 가이드 메시지를 수정했습니다. START 컴포넌트를 완전히 제거하고, DateSelect 속성을 onComplete 콜백과 activeType으로 변경했습니다.
온보딩 상수 및 유틸리티
src/pages/onboarding/constants/onboarding.ts, src/pages/onboarding/utils/onboarding-button.ts
FIRST_FUNNEL_STEPS 시퀀스를 업데이트하고 MATCHING_TYPE 라벨을 수정했습니다. getButtonLabel을 제거하고 handleButtonClick을 리팩토링하여 FREQUENCY 검증과 avgSeason 페이로드를 추가했습니다.
매칭 API 및 타입
src/shared/apis/match/match-mutations.ts, src/shared/types/match-types.ts, src/shared/types/user-types.ts, src/shared/constants/api.ts, src/shared/constants/query-key.ts
MATCH_CONDITION 뮤테이션 페이로드에서 genderPreference를 제거하고 avgSeason을 추가했습니다. DELETE_MATCH_CONDITION 엔드포인트 및 쿼리 키를 추가했습니다. API 버전을 /v1에서 /v3으로 업그레이드했습니다.
게임 매칭 바텀시트
src/shared/components/bottom-sheet/game-match/game-match-bottom-sheet.tsx, src/shared/components/bottom-sheet/game-match/game-match-footer.tsx
groupRole 속성을 onComplete 콜백으로 교체했습니다. 버튼 라벨을 "맞춤 매칭 생성하기"에서 "다음으로"로 변경하고, 주의 메시지 UI를 제거했습니다.
프로필 편집 및 유틸리티
src/pages/edit-profile/edit-profile.tsx, src/shared/utils/show-error-toast.tsx, src/pages/match/constants/matching.ts, src/pages/sign-up/sign-up.tsx
성별 선호도 필드의 초기 상태를 변경했습니다. showErrorToast 배경색을 수정했고, MATCHING_SUGGESTION_MESSAGE_TITLE 함수를 추가했으며, useFunnel 호출에 currentStep을 전달했습니다.

🎭 시퀀스 다이어그램

sequenceDiagram
    actor User
    participant Onboarding as Onboarding Flow
    participant Funnel as Funnel Component
    participant DateSelect as DateSelect Component
    participant GameMatch as GameMatchBottomSheet
    participant API as Backend API
    participant Complete as Complete Component

    User->>Onboarding: 온보딩 시작
    Onboarding->>Funnel: FREQUENCY 단계 렌더링 (currentStep prop 전달)
    Funnel->>Onboarding: 해당 Step만 반환
    User->>Onboarding: 직관 횟수 입력 후 다음으로
    
    Onboarding->>Funnel: DATE_SELECT 단계 렌더링
    Funnel->>DateSelect: activeType과 onComplete 콜백 전달
    User->>GameMatch: 경기 날짜 선택 후 제출
    GameMatch->>API: 매칭 생성 요청 (avgSeason 포함)
    API-->>GameMatch: matchId 응답
    
    GameMatch->>DateSelect: onComplete(matchId) 콜백 실행
    DateSelect->>Onboarding: createdMatch 상태 저장 후 goNext()
    
    Onboarding->>Funnel: COMPLETE 단계 렌더링
    Funnel->>Complete: matchId, type, nickname 전달
    Complete->>Complete: 타입에 따라 CompleteSingleCard 또는 CompleteGroupCard 렌더링
    User->>Complete: 완료 버튼 클릭
    Complete->>Onboarding: HOME으로 네비게이션
Loading

🎯 예상 코드 리뷰 노력

🎯 4 (복잡) | ⏱️ ~45분

🔗 관련 PR

👥 제안된 검토자

  • heesunee
  • Dubabbi

🐰 시의 축하

🐇 ✨ 온보딩이 깔끔해지고,
계절마다 직관 횟수를 세네요,
단계도 정리되고, 타입도 갈려,
매칭 카드 예쁘게 펼쳐져,
메이트를 찾는 여정이 더 쉬워져! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 주요 변경 사항을 명확하게 요약하고 있습니다. 매칭 조건 설정 페이지 수정이라는 핵심 목표를 간결하고 구체적으로 표현했습니다.
Linked Issues check ✅ Passed PR 변경사항이 #398 이슈의 모든 주요 요구사항을 충족합니다: 텍스트 수정, 시즌 평균 직관 수 페이지 제작, 캘린더 이전 날짜 비활성화, 날짜 제한 토스트 삭제, 매칭 생성 제안 페이지 제작 등이 구현되었습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 #398 이슈의 범위 내에 있으며, 매칭 조건 설정 페이지 개선과 직접적으로 관련이 있습니다. 성별 필드 제거, API 엔드포인트 업그레이드, UI 텍스트 개선 등이 일관되게 목표를 지원합니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/#398/initial-matching-condition
📝 Coding Plan
  • Generate coding plan for human review comments

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@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: 8

Caution

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

⚠️ Outside diff range comments (2)
src/pages/edit-profile/edit-profile.tsx (1)

74-103: ⚠️ Potential issue | 🟠 Major

성별 초기값이 빈 문자열로 고정되어 저장 시 값 유실 위험이 있습니다.

initial.gender''로 고정하면, 사용자가 성별을 건드리지 않고 다른 항목만 저장해도 genderPreference가 빈 값으로 전송될 수 있습니다(Line 98-103). 기존 서버값을 초기값으로 유지해야 안전합니다.

🛠️ 제안 수정안
   const initial = {
     team: data?.team ?? '',
-    gender: '',
+    gender: data?.genderPreference ?? '',
     mateTeam: data?.teamAllowed ?? '',
     viewStyle: data?.style ?? '',
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/edit-profile/edit-profile.tsx` around lines 74 - 103,
initial.gender is hard-coded to '' causing genderPreference to be overwritten on
save; change initial to pull the server value (e.g., data?.gender or
data?.genderPreference) instead of always '' so that genderValue (and
consequently genderPreference sent in editMatchCondition inside handleSaveClick)
retains the original server value when the user doesn't touch the gender field;
update the initialization where initial is defined and ensure genderValue still
reads from gender ?? initial.gender.
src/shared/types/user-types.ts (1)

72-77: ⚠️ Potential issue | 🟠 Major

중복된 타입 정의로 인한 불일치 문제

postMatchConditionRequestsrc/shared/types/match-types.ts에도 정의되어 있습니다. 두 파일의 정의가 다릅니다:

  • match-types.ts: team, teamAllowed, avgSeason, style (genderPreference 없음)
  • user-types.ts: team, teamAllowed, style, genderPreference (avgSeason 없음)

어떤 타입이 import되느냐에 따라 다른 동작이 발생할 수 있습니다. 타입 정의를 한 곳으로 통합하거나, 이 파일의 정의도 match-types.ts와 동일하게 업데이트해야 합니다.

🔧 수정 제안
 export interface postMatchConditionRequest {
   team: string;
   teamAllowed: string | null;
+  avgSeason: number;
   style: string;
-  genderPreference: string;
 }

또는 중복을 제거하고 match-types.ts에서 re-export하는 것을 권장합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/types/user-types.ts` around lines 72 - 77,
postMatchConditionRequest is duplicated with a conflicting shape between
src/shared/types/user-types.ts and match-types.ts; update user-types.ts so the
interface matches the canonical definition in match-types.ts (include avgSeason
and remove or make genderPreference optional to match), or remove the duplicate
and re-export the type from match-types.ts (e.g., export {
postMatchConditionRequest } from './match-types') so there is a single source of
truth; locate the interface named postMatchConditionRequest and either align its
fields to match-types.ts or replace its declaration with a re-export.
🧹 Nitpick comments (4)
src/shared/components/bottom-sheet/game-match/game-match-bottom-sheet.tsx (2)

96-96: 중복된 .toString() 호출

createdMatchId는 이미 79번 라인에서 response.matchId.toString()으로 문자열로 변환되어 있습니다. 다시 .toString()을 호출할 필요가 없습니다.

♻️ 수정 제안
-            navigate(`${ROUTES.MATCH_CREATE(createdMatchId.toString())}?type=${queryType}`);
+            navigate(`${ROUTES.MATCH_CREATE(createdMatchId)}?type=${queryType}`);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/components/bottom-sheet/game-match/game-match-bottom-sheet.tsx` at
line 96, createdMatchId is already a string (converted earlier), so remove the
redundant .toString() when building the route; update the navigate call that
uses ROUTES.MATCH_CREATE(createdMatchId.toString()) to pass createdMatchId
directly (keeping interpolation with ?type=${queryType}), locating the change in
the navigate invocation inside the GameMatchBottomSheet component where
createdMatchId, ROUTES.MATCH_CREATE and queryType are used.

93-94: 타입 안전성을 통한 onComplete 콜백 처리 개선 권장

현재 코드에서 fromOnboardingonComplete의 관계가 타입 수준에서 강제되지 않아, fromOnboarding={true}이지만 onComplete이 제공되지 않는 상태가 가능합니다. 실제 사용처(date-select.tsx)에서는 항상 함께 제공되고 있으나, 컴포넌트 인터페이스 설계상 이를 보장하지 않습니다.

다음과 같이 개선할 수 있습니다:

  • fromOnboarding=true일 때 onComplete을 필수 필드로 강제하는 Union 타입 활용
  • 또는 fromOnboarding=true일 때 onComplete이 undefined인 경우 런타임 경고/에러 추가

현재는 실무상 문제가 없으나, 컴포넌트 오사용 시 조용히 실패하는 구조를 개선하는 것이 권장됩니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/components/bottom-sheet/game-match/game-match-bottom-sheet.tsx`
around lines 93 - 94, The branch that calls onComplete when fromOnboarding is
true is not type-safe; update the GameMatchBottomSheet props to a discriminated
union so that when fromOnboarding: true the onComplete callback is required
(e.g. type Props = { fromOnboarding: true; onComplete: (id:string)=>void; ... }
| { fromOnboarding?: false; onComplete?: ... }), and adjust the component
signature to use that Props type so the compiler enforces presence of
onComplete; alternatively (or additionally) add a runtime guard in the component
before calling onComplete: if (fromOnboarding && !onComplete) {
console.error("GameMatchBottomSheet: onComplete required when fromOnboarding is
true"); return; } referencing the fromOnboarding prop and onComplete handler
used in the component.
src/shared/hooks/use-funnel.tsx (1)

15-20: 타입 단언이 런타임 안전성을 보장하지 않습니다.

Children.toArray의 결과에 ReactElement<{ name: string }>[]로 타입 단언을 적용하고 있지만, 실제로 문자열이나 숫자 같은 비-엘리먼트 자식이 전달되면 child.props.nameundefined가 되어 매칭에 실패할 수 있습니다.

🛡️ 타입 가드를 사용한 안전한 필터링 제안
+import { Children, isValidElement, type ReactElement, type ReactNode, useCallback, useEffect } from 'react';
+
 const Funnel = ({ children, currentStep }: FunnelProps) => {
-  const childrenArray = Children.toArray(children) as ReactElement<{ name: string }>[];
-  const matched = childrenArray.find((child) => child.props.name === currentStep);
+  const childrenArray = Children.toArray(children);
+  const matched = childrenArray.find(
+    (child): child is ReactElement<{ name: string }> =>
+      isValidElement(child) && child.props.name === currentStep
+  );

   return <>{matched}</>;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/hooks/use-funnel.tsx` around lines 15 - 20, The current Funnel
component unsafely asserts Children.toArray(...) as ReactElement<{ name: string
}>[], which can break at runtime if non-elements are passed; update Funnel to
first convert children via Children.toArray and then filter them with
React.isValidElement to narrow the union, e.g. produce a childrenArray of only
React elements and use a type guard that ensures element.props has a name
(reference: Funnel, childrenArray, matched), and then run find on that filtered
array so matched access to child.props.name is safe.
src/pages/onboarding/onboarding-group.tsx (1)

44-48: 불필요한 조건문 정리 필요

currentStep !== 'START' (Line 44)와 currentStep !== 'DATE_SELECT' (Line 68) 조건은 해당 스텝들이 GROUP_FUNNEL_STEPS에 없거나 주석 처리되어 항상 true입니다. TODO 주석에 따라 파일 전체 삭제 예정이라면 현재 상태로 유지해도 무방합니다.

Also applies to: 68-68

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/onboarding/onboarding-group.tsx` around lines 44 - 48, The
conditional checks comparing currentStep to hard-coded strings ('START' and
'DATE_SELECT') are redundant because those steps are not present in
GROUP_FUNNEL_STEPS; replace the string comparisons with a membership check
against GROUP_FUNNEL_STEPS (e.g., GROUP_FUNNEL_STEPS.includes(currentStep)) or
simply remove the surrounding conditional if the ProgressBar/related UI should
always render when steps exist; update the instances around ProgressBar
(currentIndex, steps) and any other conditional blocks referencing currentStep
to use GROUP_FUNNEL_STEPS.includes(currentStep) so the visibility is driven by
the actual steps list rather than stale hard-coded values.
🤖 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/match/constants/matching.ts`:
- Around line 40-41: 파일의 호칭 표기 일관성을 맞추기 위해 MATCHING_SUGGESTION_MESSAGE_TITLE 함수의
템플릿에서 `${nickname}님`을 다른 메시지들(예: `${nickname} 님`)과 동일하게 공백 포함 표기인 `${nickname}
님`으로 변경하세요; 해당 상수명 MATCHING_SUGGESTION_MESSAGE_TITLE을 찾아 템플릿 리터럴 내부의 호칭 앞에 공백을
추가하여 UI 문구 표준에 맞게 통일합니다.

In `@src/pages/onboarding/components/complete-button-section.tsx`:
- Around line 22-28: The "만들기" button currently reuses handleGoToMate (which
routes to ROUTES.HOME); change its onClick to route to the match creation path
instead by invoking the MATCH_CREATE route with the current matchId (e.g., call
router.push(ROUTES.MATCH_CREATE(matchId)) or create a small helper like
handleGoToCreate that does router.push(ROUTES.MATCH_CREATE(matchId))). Update
the Button component's onClick prop in complete-button-section.tsx to use this
new target so "만들기" navigates to ROUTES.MATCH_CREATE(matchId) rather than
ROUTES.HOME.

In `@src/pages/onboarding/components/complete-single-card.tsx`:
- Around line 6-18: CompleteSingleCard and CompleteGroupCard call
useSuspenseQuery but are rendered without a local Suspense boundary; wrap the
place where these components are used (e.g., the Step/Funnel render branch that
chooses by type) with a React Suspense providing a fallback like CardLoading so
loading and errors are handled per-card instead of relying only on the global
boundary — ensure the Suspense wraps the expression that renders
CompleteSingleCard/CompleteGroupCard and import/use CardLoading as the fallback.

In `@src/pages/onboarding/components/complete.tsx`:
- Around line 11-17: The Complete component renders
MATCHING_SUGGESTION_MESSAGE_TITLE(nickname) but does not handle empty nickname;
update the rendering logic in Complete (and/or upstream before calling it) to
provide a fallback when nickname is falsy (e.g., use a default like "회원" or "님"
or a generic phrase) so MATCHING_SUGGESTION_MESSAGE_TITLE is never called with
an empty string; locate Complete and the MATCHING_SUGGESTION_MESSAGE_TITLE usage
and ensure nickname is normalized (e.g., nickname || DEFAULT_NICKNAME) or
conditionally render an alternative message when nickname is missing.

In `@src/pages/onboarding/onboarding.tsx`:
- Around line 148-152: When currentStep is FREQUENCY and the user previously
chose NO_TEAM_OPTION, handlePrev should mirror the forward skip logic by routing
back to SUPPORT_TEAM instead of goPrev() to SYNC_SUPPORT_TEAM; update the
handlePrev function to check if currentStep === 'FREQUENCY' &&
selections.SUPPORT_TEAM === NO_TEAM_OPTION and call goTo('SUPPORT_TEAM') (and
adjust selections.SYNC_SUPPORT_TEAM if needed) before falling back to the normal
goPrev() behavior so backward navigation matches the forward skip implemented in
the currentStep === 'SUPPORT_TEAM' block.

In `@src/pages/onboarding/utils/onboarding-button.ts`:
- Around line 40-51: In the MATCHING_TYPE branch validate selections.FREQUENCY
before calling matchMutate: ensure selections.FREQUENCY is non-null/non-empty
and converts to a finite number (e.g., Number(...) not NaN) and return early if
invalid; then pass the validated numeric value as avgSeason. Update the block
that checks SUPPORT_TEAM and VIEWING_STYLE (symbols: currentStep, MATCHING_TYPE,
selections, selections.FREQUENCY, SUPPORT_TEAM, VIEWING_STYLE,
parsedTeamAllowed, matchMutate) to include this frequency validation so
avgSeason is always a valid number.

In `@src/shared/constants/api.ts`:
- Line 34: DELETE_MATCH_CONDITION is missing the leading slash and will cause
incorrect URL composition with axios; update the constant DELETE_MATCH_CONDITION
to include the leading '/' (making it '/v3/users/match-condition') so it matches
POST_MATCH_CONDITION and other endpoints and avoids baseURL concatenation
errors.

In `@src/shared/constants/query-key.ts`:
- Around line 62-65: DELETE.MATCH()와 DELETE.CONDITION() currently both return
the same key (via MATCH_KEY.ALL + 'delete'), causing unintended cache
invalidation; change one to return a distinct segment (e.g., have
DELETE.CONDITION() return [...MATCH_KEY.ALL, 'condition', 'delete'] or
['match','condition','delete']) so the two keys differ; update the functions
DELETE.MATCH and DELETE.CONDITION in the query-key constants to use the new
unique segment while still based on MATCH_KEY.ALL.

---

Outside diff comments:
In `@src/pages/edit-profile/edit-profile.tsx`:
- Around line 74-103: initial.gender is hard-coded to '' causing
genderPreference to be overwritten on save; change initial to pull the server
value (e.g., data?.gender or data?.genderPreference) instead of always '' so
that genderValue (and consequently genderPreference sent in editMatchCondition
inside handleSaveClick) retains the original server value when the user doesn't
touch the gender field; update the initialization where initial is defined and
ensure genderValue still reads from gender ?? initial.gender.

In `@src/shared/types/user-types.ts`:
- Around line 72-77: postMatchConditionRequest is duplicated with a conflicting
shape between src/shared/types/user-types.ts and match-types.ts; update
user-types.ts so the interface matches the canonical definition in
match-types.ts (include avgSeason and remove or make genderPreference optional
to match), or remove the duplicate and re-export the type from match-types.ts
(e.g., export { postMatchConditionRequest } from './match-types') so there is a
single source of truth; locate the interface named postMatchConditionRequest and
either align its fields to match-types.ts or replace its declaration with a
re-export.

---

Nitpick comments:
In `@src/pages/onboarding/onboarding-group.tsx`:
- Around line 44-48: The conditional checks comparing currentStep to hard-coded
strings ('START' and 'DATE_SELECT') are redundant because those steps are not
present in GROUP_FUNNEL_STEPS; replace the string comparisons with a membership
check against GROUP_FUNNEL_STEPS (e.g.,
GROUP_FUNNEL_STEPS.includes(currentStep)) or simply remove the surrounding
conditional if the ProgressBar/related UI should always render when steps exist;
update the instances around ProgressBar (currentIndex, steps) and any other
conditional blocks referencing currentStep to use
GROUP_FUNNEL_STEPS.includes(currentStep) so the visibility is driven by the
actual steps list rather than stale hard-coded values.

In `@src/shared/components/bottom-sheet/game-match/game-match-bottom-sheet.tsx`:
- Line 96: createdMatchId is already a string (converted earlier), so remove the
redundant .toString() when building the route; update the navigate call that
uses ROUTES.MATCH_CREATE(createdMatchId.toString()) to pass createdMatchId
directly (keeping interpolation with ?type=${queryType}), locating the change in
the navigate invocation inside the GameMatchBottomSheet component where
createdMatchId, ROUTES.MATCH_CREATE and queryType are used.
- Around line 93-94: The branch that calls onComplete when fromOnboarding is
true is not type-safe; update the GameMatchBottomSheet props to a discriminated
union so that when fromOnboarding: true the onComplete callback is required
(e.g. type Props = { fromOnboarding: true; onComplete: (id:string)=>void; ... }
| { fromOnboarding?: false; onComplete?: ... }), and adjust the component
signature to use that Props type so the compiler enforces presence of
onComplete; alternatively (or additionally) add a runtime guard in the component
before calling onComplete: if (fromOnboarding && !onComplete) {
console.error("GameMatchBottomSheet: onComplete required when fromOnboarding is
true"); return; } referencing the fromOnboarding prop and onComplete handler
used in the component.

In `@src/shared/hooks/use-funnel.tsx`:
- Around line 15-20: The current Funnel component unsafely asserts
Children.toArray(...) as ReactElement<{ name: string }>[], which can break at
runtime if non-elements are passed; update Funnel to first convert children via
Children.toArray and then filter them with React.isValidElement to narrow the
union, e.g. produce a childrenArray of only React elements and use a type guard
that ensures element.props has a name (reference: Funnel, childrenArray,
matched), and then run find on that filtered array so matched access to
child.props.name is safe.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e3e7060c-2271-449d-bc49-3062427901d8

📥 Commits

Reviewing files that changed from the base of the PR and between d761b48 and 6703259.

📒 Files selected for processing (25)
  • src/pages/edit-profile/edit-profile.tsx
  • src/pages/match/constants/matching.ts
  • src/pages/onboarding/components/complete-button-section.tsx
  • src/pages/onboarding/components/complete-group-card.tsx
  • src/pages/onboarding/components/complete-single-card.tsx
  • src/pages/onboarding/components/complete.tsx
  • src/pages/onboarding/components/date-select.tsx
  • src/pages/onboarding/components/frequency.tsx
  • src/pages/onboarding/components/matching-type.tsx
  • src/pages/onboarding/components/start.tsx
  • src/pages/onboarding/components/sync-support-team.tsx
  • src/pages/onboarding/constants/onboarding.ts
  • src/pages/onboarding/onboarding-group.tsx
  • src/pages/onboarding/onboarding.tsx
  • src/pages/onboarding/utils/onboarding-button.ts
  • src/pages/sign-up/sign-up.tsx
  • src/shared/apis/match/match-mutations.ts
  • src/shared/components/bottom-sheet/game-match/game-match-bottom-sheet.tsx
  • src/shared/components/bottom-sheet/game-match/game-match-footer.tsx
  • src/shared/constants/api.ts
  • src/shared/constants/query-key.ts
  • src/shared/hooks/use-funnel.tsx
  • src/shared/types/match-types.ts
  • src/shared/types/user-types.ts
  • src/shared/utils/show-error-toast.tsx
💤 Files with no reviewable changes (1)
  • src/pages/onboarding/components/start.tsx

Comment on lines +6 to +18
const CompleteSingleCard = ({ matchId }: { matchId: number }) => {
const { data } = useSuspenseQuery(matchQueries.SINGLE_MATCH_RESULT(matchId));

const cardProps: CardProps = {
...data,
type: 'single',
imgUrl: [data.imgUrl],
chips: [data.team, data.style] as ChipColor[],
className: 'w-full',
};

return <Card isCreated {...cardProps} />;
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check if Complete component or its parents are wrapped with Suspense boundary
rg -n -B5 -A10 'CompleteSingleCard|CompleteGroupCard|<Complete' --type=tsx --type=ts

Repository: MATEBALL/MATEBALL-CLIENT

Length of output: 95


🏁 Script executed:

#!/bin/bash
# Fix: Use correct rg syntax for TypeScript files
# Search for CompleteSingleCard and CompleteGroupCard usage
rg -n -B5 -A10 'CompleteSingleCard|CompleteGroupCard|<Complete' -t ts

Repository: MATEBALL/MATEBALL-CLIENT

Length of output: 8866


🏁 Script executed:

#!/bin/bash
# Also search for onboarding.tsx specifically to see how Complete components are rendered
fd -t f 'onboarding' | head -20

Repository: MATEBALL/MATEBALL-CLIENT

Length of output: 333


🏁 Script executed:

#!/bin/bash
# Look for any Suspense imports to understand the pattern in the codebase
rg -n 'import.*Suspense|from.*react' -t ts | grep -i suspense | head -20

Repository: MATEBALL/MATEBALL-CLIENT

Length of output: 755


🏁 Script executed:

#!/bin/bash
# Find Step and Funnel component definitions
fd -t f 'step\|funnel' -i | grep -E '\.(tsx|ts)$' | head -20

Repository: MATEBALL/MATEBALL-CLIENT

Length of output: 50


🏁 Script executed:

#!/bin/bash
# Search for Step component usage and definition
rg -n 'Step.*from|const Step.*=|function Step' -t ts | head -20

Repository: MATEBALL/MATEBALL-CLIENT

Length of output: 1139


🏁 Script executed:

#!/bin/bash
# Check the onboarding.tsx file more completely to see overall structure
cat -n src/pages/onboarding/onboarding.tsx | head -160

Repository: MATEBALL/MATEBALL-CLIENT

Length of output: 6870


🏁 Script executed:

#!/bin/bash
# Check the Funnel component definition in use-funnel.tsx
cat -n src/shared/hooks/use-funnel.tsx

Repository: MATEBALL/MATEBALL-CLIENT

Length of output: 2741


🏁 Script executed:

#!/bin/bash
# Check if there's a root layout or app component that provides Suspense
rg -n 'Suspense' -A5 -B5 src/shared/routes/global-error-boundary.tsx

Repository: MATEBALL/MATEBALL-CLIENT

Length of output: 636


🏁 Script executed:

#!/bin/bash
# Check how Onboarding component is used/imported in the main app
rg -n 'Onboarding' --type ts src/ | grep -E 'import|from|export' | head -15

Repository: MATEBALL/MATEBALL-CLIENT

Length of output: 1710


useSuspenseQuery 사용 시 전용 Suspense 바운더리 필요

CompleteSingleCardCompleteGroupCarduseSuspenseQuery로 데이터를 로드하는데, 현재 Step/Funnel 컴포넌트는 Suspense를 제공하지 않습니다. 전역 GlobalErrorBoundary의 Suspense가 catch하지만, 카드별 전용 Suspense 바운더리를 추가하여 로딩 상태와 에러 처리를 더 세밀하게 제어할 수 있습니다.

<Suspense fallback={<CardLoading />}>
  {type === 'single' ? (
    <CompleteSingleCard matchId={matchId} />
  ) : (
    <CompleteGroupCard matchId={matchId} />
  )}
</Suspense>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/onboarding/components/complete-single-card.tsx` around lines 6 -
18, CompleteSingleCard and CompleteGroupCard call useSuspenseQuery but are
rendered without a local Suspense boundary; wrap the place where these
components are used (e.g., the Step/Funnel render branch that chooses by type)
with a React Suspense providing a fallback like CardLoading so loading and
errors are handled per-card instead of relying only on the global boundary —
ensure the Suspense wraps the expression that renders
CompleteSingleCard/CompleteGroupCard and import/use CardLoading as the fallback.

Comment on lines +11 to +17
const Complete = ({ nickname, matchId, type }: CompleteProps) => {
return (
<div className="flex-1 flex-col-center gap-[4rem]">
<p className="title_24_sb text-center text-gray-black">
매칭 조건 설정이 끝났어요!
<br />
메이트를 찾아볼까요?
</p>
<div className="w-full flex-1 flex-col-between gap-[4rem] whitespace-pre-line pt-[6.45rem]">
<div className="w-full flex-col gap-[4rem] px-[1.6rem]">
<p className="title_24_sb text-center text-gray-black">
{MATCHING_SUGGESTION_MESSAGE_TITLE(nickname)}
</p>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

nickname prop이 빈 문자열로 전달될 경우 UI 문제

MATCHING_SUGGESTION_MESSAGE_TITLE(nickname)"${nickname}님을 위한..." 형식의 메시지를 생성합니다. onboarding.tsx에서 nickname=""으로 전달하면 "님을 위한\n매칭카드를 만들어 드릴까요?"가 표시되어 자연스럽지 않습니다. 실제 사용자 닉네임을 전달하거나, 닉네임이 없을 경우의 fallback 메시지 처리가 필요합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/onboarding/components/complete.tsx` around lines 11 - 17, The
Complete component renders MATCHING_SUGGESTION_MESSAGE_TITLE(nickname) but does
not handle empty nickname; update the rendering logic in Complete (and/or
upstream before calling it) to provide a fallback when nickname is falsy (e.g.,
use a default like "회원" or "님" or a generic phrase) so
MATCHING_SUGGESTION_MESSAGE_TITLE is never called with an empty string; locate
Complete and the MATCHING_SUGGESTION_MESSAGE_TITLE usage and ensure nickname is
normalized (e.g., nickname || DEFAULT_NICKNAME) or conditionally render an
alternative message when nickname is missing.

Comment on lines +40 to +51
if (currentStep === 'MATCHING_TYPE') {
const { SUPPORT_TEAM, SYNC_SUPPORT_TEAM, VIEWING_STYLE } = selections;
const parsedTeamAllowed = SYNC_SUPPORT_TEAM === '상관없어요' ? null : SYNC_SUPPORT_TEAM;

if (!SUPPORT_TEAM || !VIEWING_STYLE || !GENDER) return;
if (!SUPPORT_TEAM || !VIEWING_STYLE) return;

if (matchMutate) {
matchMutate(
{
team: SUPPORT_TEAM,
teamAllowed: parsedTeamAllowed,
style: VIEWING_STYLE,
genderPreference: GENDER,
},
{
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: authQueries.USER_STATUS().queryKey });
if (selections.MATCHING_TYPE === '1:1 매칭') {
gaEvent('condition_set_completed', { match_type: 'one_to_one' });
goNext();
} else {
gaEvent('condition_set_completed', { match_type: 'group' });
if (setProgressOverride) {
setProgressOverride(0);
setTimeout(() => {
navigate(ROUTES.ONBOARDING_GROUP);
}, 300);
}
}
},
matchMutate?.(
{
team: SUPPORT_TEAM,
teamAllowed: parsedTeamAllowed,
avgSeason: Number(selections.FREQUENCY),
style: VIEWING_STYLE,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

FREQUENCY 값 검증 누락

MATCHING_TYPE 스텝에서 matchMutate를 호출할 때 SUPPORT_TEAMVIEWING_STYLE만 검증하고 FREQUENCY는 검증하지 않습니다. selections.FREQUENCY가 null이거나 빈 문자열이면 Number()0 또는 NaN을 반환할 수 있습니다.

🛡️ 검증 추가 제안
  if (currentStep === 'MATCHING_TYPE') {
-   const { SUPPORT_TEAM, SYNC_SUPPORT_TEAM, VIEWING_STYLE } = selections;
+   const { SUPPORT_TEAM, SYNC_SUPPORT_TEAM, VIEWING_STYLE, FREQUENCY } = selections;
    const parsedTeamAllowed = SYNC_SUPPORT_TEAM === '상관없어요' ? null : SYNC_SUPPORT_TEAM;

-   if (!SUPPORT_TEAM || !VIEWING_STYLE) return;
+   if (!SUPPORT_TEAM || !VIEWING_STYLE || !FREQUENCY) return;

    matchMutate?.(
      {
        team: SUPPORT_TEAM,
        teamAllowed: parsedTeamAllowed,
-       avgSeason: Number(selections.FREQUENCY),
+       avgSeason: Number(FREQUENCY),
        style: VIEWING_STYLE,
      },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/onboarding/utils/onboarding-button.ts` around lines 40 - 51, In the
MATCHING_TYPE branch validate selections.FREQUENCY before calling matchMutate:
ensure selections.FREQUENCY is non-null/non-empty and converts to a finite
number (e.g., Number(...) not NaN) and return early if invalid; then pass the
validated numeric value as avgSeason. Update the block that checks SUPPORT_TEAM
and VIEWING_STYLE (symbols: currentStep, MATCHING_TYPE, selections,
selections.FREQUENCY, SUPPORT_TEAM, VIEWING_STYLE, parsedTeamAllowed,
matchMutate) to include this frequency validation so avgSeason is always a valid
number.

Comment on lines 62 to 65
DELETE: {
MATCH: () => [...MATCH_KEY.ALL, 'delete'] as const,
CONDITION: () => [...MATCH_KEY.ALL, 'delete'] as const,
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

중복된 쿼리 키로 인해 의도치 않은 캐시 무효화 발생 가능

DELETE.MATCH()DELETE.CONDITION() 모두 동일한 키 ['match', 'delete']를 반환합니다. 이로 인해 하나를 무효화하면 다른 쪽도 함께 무효화됩니다.

🔧 고유한 키 세그먼트 추가 제안
   DELETE: {
     MATCH: () => [...MATCH_KEY.ALL, 'delete'] as const,
-    CONDITION: () => [...MATCH_KEY.ALL, 'delete'] as const,
+    CONDITION: () => [...MATCH_KEY.ALL, 'delete', 'condition'] as const,
   },
📝 Committable suggestion

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

Suggested change
DELETE: {
MATCH: () => [...MATCH_KEY.ALL, 'delete'] as const,
CONDITION: () => [...MATCH_KEY.ALL, 'delete'] as const,
},
DELETE: {
MATCH: () => [...MATCH_KEY.ALL, 'delete'] as const,
CONDITION: () => [...MATCH_KEY.ALL, 'delete', 'condition'] as const,
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/constants/query-key.ts` around lines 62 - 65, DELETE.MATCH()와
DELETE.CONDITION() currently both return the same key (via MATCH_KEY.ALL +
'delete'), causing unintended cache invalidation; change one to return a
distinct segment (e.g., have DELETE.CONDITION() return [...MATCH_KEY.ALL,
'condition', 'delete'] or ['match','condition','delete']) so the two keys
differ; update the functions DELETE.MATCH and DELETE.CONDITION in the query-key
constants to use the new unique segment while still based on MATCH_KEY.ALL.

@MATEBALL MATEBALL deleted a comment from coderabbitai bot Mar 20, 2026
@MATEBALL MATEBALL deleted a comment from coderabbitai bot Mar 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: 매칭 조건 설정 페이지를 수정합니다.

1 participant