Conversation
|
Warning Rate limit exceeded@heesunee has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 12 minutes and 43 seconds before requesting another review. ⌛ 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. 📒 Files selected for processing (3)
Walkthrough회원가입 흐름을 v2 API로 재구성. 약관 동의(AGREEMENT_INFO) 요청을 먼저 호출한 뒤 사용자 정보(USER_INFO: nickname, introduction, birthYear, gender)를 단일로 전송하도록 변경. CheckboxRow에 외부 link prop 추가 및 agreement-step에 TERMS/PRIVACY 링크 주입. 스키마·상수명 information → introduction으로 변경. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User as 사용자
participant AgreementStep as AgreementStep
participant CheckboxRow as CheckboxRow
participant SignupStep as SignupStep
participant Mutations as userMutations
participant API as Backend
Note over User,AgreementStep: 약관 동의 단계
User->>CheckboxRow: 약관 체크 및 링크 확인
User->>AgreementStep: 약관 동의 제출
AgreementStep->>Mutations: AGREEEMENT_INFO.mutateAsync({ hasAccepted: true })
Mutations->>API: POST /v2/users/consent
API-->>Mutations: 200 OK
Mutations-->>AgreementStep: success
Note over User,SignupStep: 사용자 정보 제출 단계
User->>SignupStep: 닉네임·소개·출생연도·성별 입력 후 제출
SignupStep->>Mutations: USER_INFO.mutateAsync({ nickname, introduction, birthYear, gender })
Mutations->>API: POST /v2/users/info
API-->>Mutations: 200 OK
Mutations-->>SignupStep: success (invalidate USER_KEY.ALL & router.navigate HOME)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Assessment against linked issues
Possibly related PRs
Suggested reviewers
Poem
✨ 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: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/pages/sign-up/components/checkbox-row.tsx (1)
15-29: 체크 영역 접근성/터치 타겟 확대아이콘만 버튼으로 클릭 가능하고 라벨은 비활성입니다. 버튼에 라벨까지 포함하고 WAI-ARIA(
role="checkbox",aria-checked)를 부여해 키보드/스크린리더 접근성을 개선하세요.- <div className="flex items-center gap-[0.8rem]"> - <button type="button" className="cursor-pointer" onClick={onClick}> - <Icon name="check-filled" className={checked ? 'text-main-800' : 'text-gray-300'} /> - </button> - <span className="body_16_m">{label}</span> - </div> + <button + type="button" + onClick={onClick} + role="checkbox" + aria-checked={checked} + className="flex items-center gap-[0.8rem] cursor-pointer" + > + <Icon name="check-filled" className={checked ? 'text-main-800' : 'text-gray-300'} /> + <span className="body_16_m">{label}</span> + </button>src/pages/sign-up/schema/validation-schema.ts (1)
14-43: 출생연도 유효 범위 검증 추가 + 소개문구 에러 메시지 명시
- birthYear는 4자리 숫자만 검증되어 과거/미래 연도(예: 0000, 3000)도 통과합니다. 합리적 범위(예: 1900~현재년도)로 제한하세요.
- introduction은 min/max만 있고 사용자 메시지가 없습니다. 간단한 안내 메시지를 추가하면 UX가 좋아집니다.
birthYear: z .string() .min(4, { message: BIRTH_ERROR_MESSAGES.LENGTH }) .max(4, { message: BIRTH_ERROR_MESSAGES.LENGTH }) - .regex(/^\d+$/, { message: BIRTH_ERROR_MESSAGES.NUMBER }), + .regex(/^\d+$/, { message: BIRTH_ERROR_MESSAGES.NUMBER }) + .refine((val) => { + const year = Number(val); + const now = new Date().getFullYear(); + return year >= 1900 && year <= now; + }, { message: '유효한 출생 연도를 입력해 주세요.' }), @@ - introduction: z.string().trim().min(INTRODUCTION_MIN_LENGTH).max(INTRODUCTION_MAX_LENGTH), + introduction: z + .string() + .trim() + .min(INTRODUCTION_MIN_LENGTH, { message: '1자 이상 입력해 주세요.' }) + .max(INTRODUCTION_MAX_LENGTH, { message: `최대 ${INTRODUCTION_MAX_LENGTH}자까지 입력 가능해요.` }),src/pages/sign-up/components/signup-step.tsx (1)
155-160: 중복 제출 방지: 진행 중(disabled) 처리 추가mutateAsync 연쇄 호출 중에는 버튼을 비활성화해 중복 API 호출을 방지하세요.
- <Button + {/* 진행 중 상태 병합 */} + <Button label="가입하기" className="w-full" ariaLabel="가입하기" type="submit" - disabled={!isValid} + disabled={ + !isValid || + userInfoMutation.isPending || + agreementInfoMutation.isPending + } />
🧹 Nitpick comments (8)
src/shared/constants/api.ts (1)
10-11: v2 엔드포인트 추가는 OK이지만, 상수 네이밍 컨벤션 일치 필요동일 파일 내
GET_*,POST_*접두어 컨벤션과 달리AGREEMENT_INFO,USER_INFO는 HTTP 의도가 드러나지 않습니다. 후속 유지보수성을 위해POST_AGREEMENT_CONSENT,POST_USER_INFO등으로 맞추는 것을 권장합니다.- AGREEMENT_INFO: '/v2/users/consent', - USER_INFO: '/v2/users/info', + POST_AGREEMENT_CONSENT: '/v2/users/consent', + POST_USER_INFO: '/v2/users/info',src/pages/sign-up/constants/LINK.ts (1)
1-3: 외부 링크 하드코딩 최소화운영 중 링크 변경 가능성이 높으니 .env 혹은 서버에서 내려주는 설정으로 분리하면 핫픽스 비용을 줄일 수 있습니다. 최소한 주석으로 소스(노션 페이지 소유자)와 변경 절차를 명시해 주세요.
src/pages/sign-up/components/checkbox-row.tsx (1)
12-12:link?: string타입 가드 보강 제안잘못된 URL로 인한 런타임 이슈를 줄이려면
link?: \https://${string}` | `http://${string}`등의 템플릿 리터럴 타입으로 제한하거나, 내부에서try { new URL(link) }` 검증을 고려하세요.src/shared/types/user-types.ts (1)
17-26: API 페이로드 스키마 정렬: gender 값 포맷 재확인UI는 '여성'/'남성'을 사용 중인데, 서버가 동일 문자열을 기대하는지('FEMALE'/'MALE' 코드 대비)를 백엔드와 재확인해 주세요. 불일치 시 중앙 매핑 계층이 필요합니다.
서버가 한글 값을 그대로 받는 것이 확정이라면 아래처럼 명시적 타입으로 문서화해 두는 것을 권장합니다.
-export interface postUserInfoRequest { +type Gender = '여성' | '남성'; +export interface postUserInfoRequest { nickname: string; introduction: string; birthYear: number; - gender: string; + gender: Gender; }Also applies to: 29-35
src/shared/apis/user/user-mutations.ts (2)
15-16: 응답 타입 안전성 보강: post 제네릭 지정post 래퍼에 제네릭을 명시해 응답 타입을 강제하세요. 추론 실패/any 누수 방지에 유효합니다.
- mutationFn: ({ nickname, introduction, birthYear, gender }) => - post(END_POINT.USER_INFO, { nickname, introduction, birthYear, gender }), + mutationFn: ({ nickname, introduction, birthYear, gender }) => + post<responseTypes>(END_POINT.USER_INFO, { nickname, introduction, birthYear, gender }), @@ - mutationFn: ({ hasAccepted }) => post(END_POINT.AGREEMENT_INFO, { hasAccepted }), + mutationFn: ({ hasAccepted }) => post<responseTypes>(END_POINT.AGREEMENT_INFO, { hasAccepted }),Also applies to: 29-29
17-24: 부가 제안: 실패 피드백 표면화현재 onError는 console.error만 수행합니다. 토스트/알럿 등 사용자 피드백 계층을 주입(예: error boundary, 전역 toast)해 주세요. AGREEMENT_INFO에도 동일 권장.
원하시면 공통 errorHandler 유틸/토스트 훅을 제안 드립니다.
src/pages/sign-up/components/signup-step.tsx (2)
31-35: defaultValues의 gender: undefined 제거 (스키마와 타입 일치)Zod 스키마상 gender는 필수 enum입니다. 초기값을 명시하지 않거나 빈 문자열/undefined를 UI 로직으로 처리하세요(현재 setValue로 설정하므로 생략 권장).
- defaultValues: { nickname: '', gender: undefined, birthYear: '', introduction: '' }, + defaultValues: { nickname: '', birthYear: '', introduction: '' },
51-65: 실패 시 사용자 피드백 추가 제안catch 블록에서 console.error만 존재합니다. 약관 동의 실패/유저 정보 등록 실패를 구분해 사용자에게 명확한 메시지를 보여 주세요(토스트/인라인 에러).
필요 시 공통 에러 메시지 매핑과 토스트 훅 적용 패치를 제안 드립니다.
📜 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 (10)
src/pages/sign-up/components/agreement-step.tsx(2 hunks)src/pages/sign-up/components/checkbox-row.tsx(2 hunks)src/pages/sign-up/components/signup-step.tsx(4 hunks)src/pages/sign-up/constants/LINK.ts(1 hunks)src/pages/sign-up/constants/validation.ts(2 hunks)src/pages/sign-up/schema/validation-schema.ts(2 hunks)src/shared/apis/user/user-mutations.ts(1 hunks)src/shared/constants/api.ts(1 hunks)src/shared/constants/query-key.ts(1 hunks)src/shared/types/user-types.ts(1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: heesunee
PR: MATEBALL/MATEBALL-CLIENT#95
File: src/pages/sign-up/components/nickname-step.tsx:28-30
Timestamp: 2025-07-09T18:07:41.693Z
Learning: heesunee는 src/pages/sign-up/components/nickname-step.tsx의 onSubmit 핸들러 API 호출을 쿼리와 함께 통합해서 처리할 예정이므로, 이 부분에 대해 다시 언급하지 않아야 합니다.
Learnt from: heesunee
PR: MATEBALL/MATEBALL-CLIENT#78
File: src/pages/sign-up/utils/age-calculate.ts:1-4
Timestamp: 2025-07-09T09:08:46.371Z
Learning: heesunee는 나이 계산 시 한국식 나이 시스템을 사용하므로, 정확한 생일 기반 계산보다는 출생연도 기준의 간단한 계산 방식을 선호합니다.
📚 Learning: 2025-07-09T18:07:41.693Z
Learnt from: heesunee
PR: MATEBALL/MATEBALL-CLIENT#95
File: src/pages/sign-up/components/nickname-step.tsx:28-30
Timestamp: 2025-07-09T18:07:41.693Z
Learning: heesunee는 src/pages/sign-up/components/nickname-step.tsx의 onSubmit 핸들러 API 호출을 쿼리와 함께 통합해서 처리할 예정이므로, 이 부분에 대해 다시 언급하지 않아야 합니다.
Applied to files:
src/pages/sign-up/components/signup-step.tsx
📚 Learning: 2025-07-09T17:24:19.755Z
Learnt from: heesunee
PR: MATEBALL/MATEBALL-CLIENT#94
File: src/pages/sign-up/sign-up.tsx:4-4
Timestamp: 2025-07-09T17:24:19.755Z
Learning: heesunee는 src/pages/sign-up/sign-up.tsx의 isNicknameStep 하드코딩된 값을 퍼널 패턴으로 카카오 통합과 함께 나중에 처리할 예정이므로, 이 부분에 대해 다시 언급하지 않아야 합니다.
Applied to files:
src/pages/sign-up/components/signup-step.tsx
🧬 Code graph analysis (4)
src/shared/apis/user/user-mutations.ts (7)
src/shared/types/base-types.ts (1)
responseTypes(7-10)src/shared/types/user-types.ts (2)
postUserInfoRequest(21-26)postAgreementInfoRequest(33-35)src/shared/constants/query-key.ts (1)
USER_KEY(2-8)src/shared/apis/base/http.ts (1)
post(7-9)src/shared/constants/api.ts (1)
END_POINT(3-36)src/shared/routes/router.tsx (1)
router(10-39)src/shared/routes/routes-config.ts (1)
ROUTES(1-22)
src/pages/sign-up/schema/validation-schema.ts (1)
src/pages/sign-up/constants/validation.ts (2)
INTRODUCTION_MIN_LENGTH(41-41)INTRODUCTION_MAX_LENGTH(42-42)
src/pages/sign-up/components/agreement-step.tsx (1)
src/pages/sign-up/constants/LINK.ts (2)
TERMS_LINK(1-1)PRIVACY_LINK(3-3)
src/pages/sign-up/components/signup-step.tsx (5)
src/pages/sign-up/schema/validation-schema.ts (2)
UserInfoFormValues(45-45)UserInfoSchema(14-43)src/shared/apis/user/user-mutations.ts (1)
userMutations(11-31)src/shared/types/user-types.ts (1)
postUserInfoRequest(21-26)src/pages/sign-up/constants/validation.ts (2)
INTRODUCTION_PLACEHOLDER(25-26)INTRODUCTION_MAX_LENGTH(42-42)src/pages/sign-up/constants/NOTICE.ts (1)
INFORMATION_RULE_MESSAGE(17-17)
⏰ 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 (2)
src/pages/sign-up/constants/validation.ts (2)
25-26: 플레이스홀더 개행 사용 확인 필요플레이스홀더에 개행문자(
\n)가 포함되어 있습니다. 단일 라인<input>을 사용할 경우 줄바꿈이 무시되거나 레이아웃이 깨질 수 있습니다. 실제 사용 컴포넌트가<textarea>인지 확인해 주세요.
41-42: 서버 스키마와 길이 제한(1–50자) 정합성 확인백엔드 요청 스키마(예: zod/yup, OpenAPI)와 동일한 최소/최대 길이인지 확인 필요합니다. 불일치 시 서버 400 발생 가능성이 있습니다.
| link={TERMS_LINK} | ||
| /> |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
외부 링크 보안 처리 및 클릭 전파 제어 확인 필요
CheckboxRow 내부에서 외부(노션) 링크를 열 때 다음을 보장해 주세요:
- target="_blank" + rel="noopener noreferrer" 적용
- 링크 클릭 시 체크 토글이 되지 않도록 e.stopPropagation()
CheckboxRow 구현에서 위 두 가지가 누락되어 있으면 추가해 주세요.
다음 스크립트로 CheckboxRow의 앵커 구현을 확인해 주세요.
추가로, 약관/개인정보 링크는 외부 도메인이므로 새 탭 열기 정책을 제품 측과 합의했는지도 확인 부탁드립니다.
Also applies to: 63-64
🏁 Script executed:
#!/bin/bash
# CheckboxRow에서 링크 처리(target/rel/stopPropagation) 확인
FILE="$(fd -a 'checkbox-row.tsx' | head -n1)"
echo "File: $FILE"
rg -n -C3 'href=|target=|rel=|stopPropagation' "$FILE"Length of output: 604
CheckboxRow 외부 링크 클릭 시 이벤트 전파 차단 처리 필요
<a>태그 onClick 핸들러에서e.stopPropagation()호출 추가- (참고)
target="_blank"및rel="noopener noreferrer"는 이미 적용되어 있습니다 - 약관/개인정보 링크의 새 탭 열기 정책에 대해 제품 측 확인 필요
🤖 Prompt for AI Agents
In src/pages/sign-up/components/agreement-step.tsx around lines 55-56, the
external links inside the CheckboxRow are allowing click events to propagate and
trigger the row's selection; add an onClick handler to the <a> elements that
calls e.stopPropagation() (preserving existing target="_blank" and rel="noopener
noreferrer") so link clicks open the external page without toggling the
checkbox, and confirm with product whether links should continue opening in a
new tab.
| <a href={link} className="cursor-pointer" target="_blank" rel="noopener noreferrer"> | ||
| {svg} | ||
| </a> | ||
| </div> |
There was a problem hiding this comment.
link 미지정 시 빈 앵커 렌더링 방지
link가 없을 때도 <a>가 렌더링되어 비어 있는 포커스 가능한 요소가 생깁니다. 접근성/탭 순서에 악영향을 줄 수 있으므로 조건부 렌더링으로 교체하세요.
- <a href={link} className="cursor-pointer" target="_blank" rel="noopener noreferrer">
- {svg}
- </a>
+ {link ? (
+ <a href={link} className="cursor-pointer" target="_blank" rel="noopener noreferrer">
+ {svg}
+ </a>
+ ) : (
+ svg && <span aria-hidden>{svg}</span>
+ )}📝 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.
| <a href={link} className="cursor-pointer" target="_blank" rel="noopener noreferrer"> | |
| {svg} | |
| </a> | |
| </div> | |
| {link ? ( | |
| <a | |
| href={link} | |
| className="cursor-pointer" | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| > | |
| {svg} | |
| </a> | |
| ) : ( | |
| svg && <span aria-hidden>{svg}</span> | |
| )} | |
| </div> |
🤖 Prompt for AI Agents
In src/pages/sign-up/components/checkbox-row.tsx around lines 30 to 33, the code
always renders an <a> even when link is undefined which creates an empty,
focusable anchor; change to conditional rendering so that if link is truthy you
render the <a href={link} target="_blank" rel="noopener noreferrer"
className="cursor-pointer">{svg}</a>, otherwise render a non-interactive element
(e.g. a <span> or <div> with the same visual styling but no href or tabIndex) so
the SVG is not placed inside a focusable anchor when there is no link.
| const agreementInfoMutaion = useMutation(userMutations.AGREEEMENT_INFO()); | ||
|
|
There was a problem hiding this comment.
오타 정정 및 식별자 일치: agreementInfoMutaion + AGREEEMENT_INFO
- 변수: agreementInfoMutaion → agreementInfoMutation
- userMutations 키: AGREEEMENT_INFO → AGREEMENT_INFO (user-mutations.ts 변경에 맞춰 수정)
- const userInfoMutation = useMutation(userMutations.USER_INFO());
- const agreementInfoMutaion = useMutation(userMutations.AGREEEMENT_INFO());
+ const userInfoMutation = useMutation(userMutations.USER_INFO());
+ const agreementInfoMutation = useMutation(userMutations.AGREEMENT_INFO());
@@
- await agreementInfoMutaion.mutateAsync({ hasAccepted: true });
+ await agreementInfoMutation.mutateAsync({ hasAccepted: true });Also applies to: 60-62
🤖 Prompt for AI Agents
In src/pages/sign-up/components/signup-step.tsx around lines 47-48 (and also
update occurrences at lines 60-62), rename the misspelled variable
agreementInfoMutaion to agreementInfoMutation and update the mutation key call
from userMutations.AGREEEMENT_INFO() to userMutations.AGREEMENT_INFO(); update
all references to the variable in this file to use the new name and ensure
imports/types remain correct so the code compiles.
| AGREEEMENT_INFO: () => | ||
| mutationOptions<responseTypes, Error, postAgreementInfoRequest>({ | ||
| mutationKey: USER_KEY.AGREEMENT(), | ||
| mutationFn: ({ hasAccepted }) => post(END_POINT.AGREEMENT_INFO, { hasAccepted }), | ||
| }), |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
오타: AGREEEMENT_INFO(EEE) → AGREEMENT_INFO(EE)
오탈자 때문에 IDE 검색/자동완성/일관성이 떨어집니다. 공개 API 성격의 export 키는 특히 엄격히 관리해 주세요. 해당 식별자를 사용하는 모든 곳(예: signup-step.tsx) 함께 변경해야 합니다.
- AGREEEMENT_INFO: () =>
+ AGREEMENT_INFO: () =>
mutationOptions<responseTypes, Error, postAgreementInfoRequest>({
mutationKey: USER_KEY.AGREEMENT(),
mutationFn: ({ hasAccepted }) => post(END_POINT.AGREEMENT_INFO, { hasAccepted }),
}),아래 스크립트로 레포 전체의 오타 사용처를 찾아 일괄 수정해 주세요.
🏁 Script executed:
#!/bin/bash
rg -n "AGREEE(MENT|MENT_INFO|MENT\(\))" -S -C2Length of output: 850
AGREEEMENT_INFO 식별자 철자 오류 및 사용처 일괄 수정
- src/shared/apis/user/user-mutations.ts:
AGREEEMENT_INFO→AGREEMENT_INFO - src/pages/sign-up/components/signup-step.tsx:
userMutations.AGREEEMENT_INFO()→userMutations.AGREEMENT_INFO()(변수명agreementInfoMutaion→agreementInfoMutation도 함께 수정)
🤖 Prompt for AI Agents
In src/shared/apis/user/user-mutations.ts around lines 26 to 30, rename the
misspelled export AGREEEMENT_INFO to AGREEMENT_INFO (fix the identifier and any
references in its mutationKey/usage); then in
src/pages/sign-up/components/signup-step.tsx update any calls
userMutations.AGREEEMENT_INFO() to userMutations.AGREEMENT_INFO() and rename the
local variable agreementInfoMutaion to agreementInfoMutation accordingly so all
references and imports match the corrected identifier.
* style: footer 수정 (#305) * style: 마이페이지 제목, 버튼 이름 수정 (#305) * feat: divider 컴포넌트 추가 (#305) * feat: divider 추가 (#305) * feat: 문의하기, 의견 보내기 구글폼 연결 (#305) * feat: logout api 추가 (#305) * feat: 로그아웃 api 연결 (#305) * feat: 로그아웃 요청 성공시 reload 추가 (#305) * feat: 문의하기/의견보내기 링크 연결 (#305) * refactor: 링크 이동 a 태그로 변경 (#305) * feat: LOGOUT시 쿼리 삭제, 취소 (#305) * feat: 로그아웃 커서 추가 (#305) * feat: 로그아웃 api 실패시 로직 추가 (#307) * feat: logout api 실패 로직 추가 (#305) --------- Co-authored-by: heesunee <heesun729@uos.ac.kr>
* feat: 프로필 수정 상단 퍼블리싱 (#307) * feat: 프로필 수정 내 응원팀 추가 (#307) * feat: 매칭 조건 수정 리스트 추가 (#307) * feat: 조건 수정 초기값 반영 (#307) * feat: selectionGroup component 분리 (#307) * feat: 클릭시 로직 추가 (#307) * style: 불필요한 border-t 삭제 (#307) * feat: SP1 회원가입 페이지 디자인 반영사항 적용 (#304) * feat: signup-page funnel apply (#303) * feat: 약관 동의 페이지 퍼블리싱 (#303) * feat: arrow-right-18 svg add (#303) * feat: input className 받게 확장 (#303) * feat: 한줄소개 관련 유효성 추가 (#303) * feat: input textarea 분기 추가 (#303) * feat: 한줄소개 글자수 실시간 반영 (#303) * feat: 서비스 이용약관 체크박스 로직 추가 (#303) * feat: 이용약관 다음으로 이동 추가 (#303) * style: 정보입력 페이지 pb 수정 (#303) * feat: 체크박스 row 컴포넌트로 분리 (#303) * feat: 이용약관 페이지 checkboxrow 컴포넌트로 교체 (#303) * fix: 체크박스 클릭 함수 icon으로 이동 (#303) * refactor: maxLength 추가 (#303) * feat: 한줄소개 공백 입력 제거 (#303) * feat: checkbox button으로 감싸게 수정 (#303) --------- Co-authored-by: heesunee <heesun729@uos.ac.kr> * feat: 응원팀 없어요 클릭시 메이트의 응원팀 disabled (#307) * feat: 응원팀 없을때 기본 값 수정 (#307) * feat: button disabled style add (#307) * fix: 수정 취소 (#307) * fix: 수정 취소 (#307) * feat: SP1 마이페이지 수정 및 로그아웃 api 연결 (#306) * style: footer 수정 (#305) * style: 마이페이지 제목, 버튼 이름 수정 (#305) * feat: divider 컴포넌트 추가 (#305) * feat: divider 추가 (#305) * feat: 문의하기, 의견 보내기 구글폼 연결 (#305) * feat: logout api 추가 (#305) * feat: 로그아웃 api 연결 (#305) * feat: 로그아웃 요청 성공시 reload 추가 (#305) * feat: 문의하기/의견보내기 링크 연결 (#305) * refactor: 링크 이동 a 태그로 변경 (#305) * feat: LOGOUT시 쿼리 삭제, 취소 (#305) * feat: 로그아웃 커서 추가 (#305) * feat: 로그아웃 api 실패시 로직 추가 (#307) * feat: logout api 실패 로직 추가 (#305) --------- Co-authored-by: heesunee <heesun729@uos.ac.kr> * feat: 프로필 수정 상단 퍼블리싱 (#307) * feat: 프로필 수정 내 응원팀 추가 (#307) * feat: 매칭 조건 수정 리스트 추가 (#307) * feat: 조건 수정 초기값 반영 (#307) * feat: selectionGroup component 분리 (#307) * feat: 클릭시 로직 추가 (#307) * style: 불필요한 border-t 삭제 (#307) * feat: 응원팀 없어요 클릭시 메이트의 응원팀 disabled (#307) * feat: 응원팀 없을때 기본 값 수정 (#307) * feat: button disabled style add (#307) * fix: 수정 취소 (#307) * fix: 수정 취소 (#307) * feat: 프로필 수정 페이지 내 divider, 한줄 소개 input 수정(#307) * feat: 리뷰 반영 (#307) --------- Co-authored-by: heesunee <heesun729@uos.ac.kr>
Deploying mateball-client with
|
| Latest commit: |
8f7b650
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://1da6058a.mateball-client.pages.dev |
| Branch Preview URL: | https://feat--323-sp1-signup-api.mateball-client.pages.dev |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (12)
src/shared/components/footer/footer.tsx (1)
15-15: gap 축소 영향: 데스크톱에서 이전 여백감 유지하도록 반응형 보완 제안모바일 축소는 타당해 보이나, md 이상에서는 종전(4.8rem) 유지가 더 안정적일 수 있습니다.
- className={clsx('cap_12_m w-full flex-col gap-[2.4rem] px-[1.6rem] py-[3.2rem]', { + className={clsx('cap_12_m w-full flex-col gap-[2.4rem] md:gap-[4.8rem] px-[1.6rem] py-[3.2rem]', {src/shared/components/card/match-card/styles/card-variants.ts (1)
12-12: outline 두께가 기본값으로 바뀌어 UI 변동 가능 — 1px 고정 복원 제안명시적 두께(
outline-[1px]) 제거로 브라우저/테일윈드 기본 두께(보통 2px)로 변해 보일 수 있습니다. 기존 1px 룩을 유지하려면 아래처럼 복원하는 편이 안전합니다.- active: 'bg-main-200 outline outline-main-600', + active: 'bg-main-200 outline outline-[1px] outline-main-600',src/pages/edit-profile/constants/edit-profile.ts (1)
1-1: 리터럴 타입 보존으로 안전성 향상옵션 배열에
as const를 붙여 유니온 리터럴로 쓰면 오타/불일치 방지가 쉬워집니다. 추후 선택값 타입으로 재사용도 용이합니다.-export const PROFILE_SYNC_MATE = ['같은 팀 메이트', '상관없어요']; +export const PROFILE_SYNC_MATE = ['같은 팀 메이트', '상관없어요'] as const;아울러 필요 시 타입을 노출해 두면 좋습니다(파일 외 추가 코드):
export type ProfileSyncMateOption = typeof PROFILE_SYNC_MATE[number];src/shared/components/button/button/styles/button-variants.ts (1)
14-15: 비활성 스타일에 상호작용 차단 클래스 추가 권장
cursor-pointer가 베이스 클래스에 있어 시각적으로만 비활성입니다. 포인터/클릭 차단 클래스를 함께 부여해 혼란을 줄이는 편이 좋습니다.- disabled: 'bg-gray-100 text-gray-400', + disabled: 'bg-gray-100 text-gray-400 cursor-not-allowed pointer-events-none',추가로 컴포넌트 레벨에서
<button disabled>/aria-disabled도 함께 지정되는지 확인 부탁드립니다.src/shared/components/header/header.tsx (1)
14-24: /match 하위 경로에도 bg-gray-100 적용되도록 isMatch 조건 확장- const isMatch = pathname === ROUTES.MATCH; + const isMatch = + pathname === ROUTES.MATCH || pathname.startsWith(`${ROUTES.MATCH}/`);src/pages/edit-profile/mocks/mockEditData.ts (1)
1-6: 상수 재사용으로 문자열 드리프트 방지
teamAllowed: '상관없어요'가PROFILE_SYNC_MATE와 중복입니다. 상수를 참조하면 i18n/카피 변경 시 드리프트를 막을 수 있습니다. 동시에 리터럴 보호를 위해as const도 권장합니다.+import { PROFILE_SYNC_MATE } from '../constants/edit-profile'; + -export const mockEditData = { +export const mockEditData = { team: '응원하는 팀이 없어요.', - teamAllowed: '상관없어요', + teamAllowed: PROFILE_SYNC_MATE[1], style: '직관먹방러', genderPreference: '여성', -}; +} as const;src/shared/components/divider/divider.tsx (1)
21-28: 접근성: role='separator'와 aria-orientation을 추가해 주세요스크린 리더가 시각적 구분선을 더 잘 인지할 수 있도록 시맨틱 속성을 부여하는 것을 권장합니다.
return ( <div - className={clsx( + role="separator" + aria-orientation={isHorizontal ? 'horizontal' : 'vertical'} + className={clsx( isHorizontal ? `w-full ${margin || 'my-4'}` : `h-full ${margin || 'mx-4'}`, color, className, )} style={isHorizontal ? { height: `${thickness}rem` } : { width: `${thickness}rem` }} /> );src/pages/edit-profile/components/selection-group.tsx (1)
29-41: disabled 상태에서는 Button에도 진짜 disabled 속성을 전달하는 것이 안전합니다시각적 비활성화뿐 아니라 포커스/키보드 입력도 차단하려면 Button 컴포넌트가 지원한다면 disabled prop을 함께 넘겨주세요.
return ( <Button key={key} label={label} variant={disabled ? 'disabled' : isSelected ? 'skyblue' : 'gray2'} className={cn( 'cap_14_sb w-auto px-[1.6rem] py-[0.6rem]', disabled && 'cursor-not-allowed', )} - onClick={() => { + disabled={disabled} + onClick={() => { if (disabled) return; onSelect(label); }} /> );Button이 disabled prop을 지원하는지 확인 부탁드립니다. 지원하지 않는다면 추후 접근성 개선 이슈로 분리하는 것도 좋습니다.
src/pages/edit-profile/edit-profile.tsx (1)
66-74: 한 줄 소개 글자수 표시: length=0 하드코딩 대신 입력 길이를 연결UI 정확도를 위해 상태 연결을 권장합니다. (API 연동 전에 간단히 useState로 반영)
- <Input + {/* TODO: introduction 상태 연결 */} + <Input placeholder={INTRODUCTION_PLACEHOLDER} defaultMessage={INFORMATION_RULE_MESSAGE} - length={0} + length={0 /* introduction.length */} hasLength className="h-[10.4rem]" label="한 줄 소개" multiline />src/pages/profile/profile.tsx (1)
62-70: 로그아웃 버튼 포커스 스타일 고려(선택사항)키보드 접근성을 위해 포커스 가시성 클래스(예: focus:outline, focus:ring)를 추가하면 좋습니다.
- <button + <button type="button" onClick={() => logout()} aria-label="로그아웃" - className="cap_14_m cursor-pointer py-[0.8rem] text-gray-800" + className="cap_14_m cursor-pointer py-[0.8rem] text-gray-800 focus:outline-none focus:ring-2 focus:ring-skyblue-400" >src/shared/apis/user/user-mutations.ts (2)
15-17: post 제네릭 명시로 반환 타입 안정화현재 post는 제네릭 T를 인자로 받는데 호출부에서 T가 유추되지 않을 수 있습니다. 명시적으로 responseTypes를 지정해 타입 안전성을 확보하세요.
- post(END_POINT.USER_INFO, { nickname, introduction, birthYear, gender }), + post<responseTypes>(END_POINT.USER_INFO, { nickname, introduction, birthYear, gender }), - mutationFn: () => post(END_POINT.POST_AUTH_LOGOUT), + mutationFn: () => post<responseTypes>(END_POINT.POST_AUTH_LOGOUT), - mutationFn: ({ hasAccepted }) => post(END_POINT.AGREEMENT_INFO, { hasAccepted }), + mutationFn: ({ hasAccepted }) => post<responseTypes>(END_POINT.AGREEMENT_INFO, { hasAccepted }),Also applies to: 29-30, 43-44
21-23: 에러 처리 일관화(UX/로깅 표준화)console.error만으로는 UX 피드백/관측이 약합니다. 공통 에러 핸들러(토스트+Sentry 등)로 래핑해 일관되게 처리하는 걸 권장합니다.
원하시면 reportApiError 유틸을 추가해 드릴게요.
Also applies to: 35-37
📜 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 (18)
src/pages/edit-profile/components/selection-group.tsx(1 hunks)src/pages/edit-profile/constants/edit-profile.ts(1 hunks)src/pages/edit-profile/edit-profile.tsx(1 hunks)src/pages/edit-profile/mocks/mockEditData.ts(1 hunks)src/pages/profile/constants/link.ts(1 hunks)src/pages/profile/edit-profile/edit-profile.tsx(0 hunks)src/pages/profile/profile.tsx(2 hunks)src/shared/apis/user/user-mutations.ts(1 hunks)src/shared/components/button/button/styles/button-variants.ts(1 hunks)src/shared/components/card/match-card/styles/card-variants.ts(1 hunks)src/shared/components/divider/divider.tsx(1 hunks)src/shared/components/footer/constants/legal.ts(0 hunks)src/shared/components/footer/footer.tsx(2 hunks)src/shared/components/header/header.tsx(1 hunks)src/shared/components/header/utils/get-header.tsx(1 hunks)src/shared/constants/api.ts(1 hunks)src/shared/constants/query-key.ts(1 hunks)src/shared/routes/lazy.ts(1 hunks)
💤 Files with no reviewable changes (2)
- src/shared/components/footer/constants/legal.ts
- src/pages/profile/edit-profile/edit-profile.tsx
✅ Files skipped from review due to trivial changes (2)
- src/shared/components/header/utils/get-header.tsx
- src/pages/profile/constants/link.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- src/shared/constants/api.ts
- src/shared/constants/query-key.ts
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: heesunee
PR: MATEBALL/MATEBALL-CLIENT#95
File: src/pages/sign-up/components/nickname-step.tsx:28-30
Timestamp: 2025-07-09T18:07:41.693Z
Learning: heesunee는 src/pages/sign-up/components/nickname-step.tsx의 onSubmit 핸들러 API 호출을 쿼리와 함께 통합해서 처리할 예정이므로, 이 부분에 대해 다시 언급하지 않아야 합니다.
Learnt from: heesunee
PR: MATEBALL/MATEBALL-CLIENT#78
File: src/pages/sign-up/utils/age-calculate.ts:1-4
Timestamp: 2025-07-09T09:08:46.371Z
Learning: heesunee는 나이 계산 시 한국식 나이 시스템을 사용하므로, 정확한 생일 기반 계산보다는 출생연도 기준의 간단한 계산 방식을 선호합니다.
📚 Learning: 2025-06-29T18:02:42.616Z
Learnt from: heesunee
PR: MATEBALL/MATEBALL-CLIENT#7
File: src/shared/routes/Router.tsx:4-4
Timestamp: 2025-06-29T18:02:42.616Z
Learning: The Home component file in the MATEBALL-CLIENT project was renamed from Home.tsx to home.tsx, so the correct import path is 'pages/home/home'.
Applied to files:
src/shared/routes/lazy.ts
🧬 Code graph analysis (5)
src/shared/components/header/header.tsx (1)
src/shared/routes/routes-config.ts (1)
ROUTES(1-22)
src/pages/edit-profile/edit-profile.tsx (6)
src/pages/edit-profile/mocks/mockEditData.ts (1)
mockEditData(1-6)src/pages/sign-up/constants/validation.ts (1)
NICKNAME_PLACEHOLDER(21-21)src/pages/sign-up/constants/NOTICE.ts (2)
NICKNAME_RULE_MESSAGE(9-9)INFORMATION_RULE_MESSAGE(17-17)src/pages/onboarding/constants/onboarding.ts (4)
TEAMS(13-24)NO_TEAM_OPTION(26-26)VIEWING_STYLE(30-46)GENDER(48-63)src/pages/edit-profile/constants/edit-profile.ts (1)
PROFILE_SYNC_MATE(1-1)src/shared/libs/cn.ts (1)
cn(4-6)
src/pages/profile/profile.tsx (4)
src/shared/apis/user/user-queries.ts (1)
userQueries(7-21)src/shared/apis/user/user-mutations.ts (1)
userMutations(11-45)src/shared/routes/routes-config.ts (1)
ROUTES(1-22)src/pages/profile/constants/link.ts (2)
REQUEST_LINK(1-1)FEEDBACK_LINK(3-3)
src/shared/apis/user/user-mutations.ts (6)
src/shared/types/base-types.ts (1)
responseTypes(7-10)src/shared/types/user-types.ts (2)
postUserInfoRequest(21-26)postAgreementInfoRequest(33-35)src/shared/constants/query-key.ts (1)
USER_KEY(2-10)src/shared/apis/base/http.ts (1)
post(7-9)src/shared/constants/api.ts (1)
END_POINT(3-39)src/shared/routes/routes-config.ts (1)
ROUTES(1-22)
src/pages/edit-profile/components/selection-group.tsx (1)
src/shared/libs/cn.ts (1)
cn(4-6)
🪛 GitHub Check: build-and-deploy
src/pages/edit-profile/edit-profile.tsx
[failure] 15-15:
'"@pages/sign-up/constants/validation"' has no exported member named 'INFORMATION_PLACEHOLDER'. Did you mean 'INTRODUCTION_PLACEHOLDER'?
🪛 GitHub Actions: Build & Deploy
src/pages/edit-profile/edit-profile.tsx
[error] 15-15: During 'npm run build' (tsc -b && vite build): TS2724: "@pages/sign-up/constants/validation" has no exported member named 'INFORMATION_PLACEHOLDER'. Did you mean 'INTRODUCTION_PLACEHOLDER'?
🔇 Additional comments (6)
src/shared/components/footer/footer.tsx (1)
1-1: MATCHING_PLATFORM_NOTICE 전역 사용처 없음 확인
코드베이스 전체 검색 결과 해당 식별자가 더 이상 사용되지 않음을 확인했습니다. 승인합니다.src/shared/routes/lazy.ts (1)
22-22: 경로 정정 LGTMEditProfile 경로를
@pages/edit-profile/edit-profile로 정리한 변경, 라우팅 일관성 측면에서 적절합니다.src/pages/profile/profile.tsx (1)
36-41: 프로필 편집 이동 및 UI 구획 정리 LGTM라우팅/분리선/외부 링크 섹션 추가가 명확합니다. 접근성 속성(rel, aria-label)도 적절합니다.
src/shared/apis/user/user-mutations.ts (3)
40-44: LGTM — 약관 동의 mutation 구성 적절키/엔드포인트/페이로드(shape)가 명확합니다. 가입 단계에서 mutateAsync로 선행 호출하기에 적합합니다.
10-10: LGTM — 타입 import 정합성API 스펙과 맞는 타입(postAgreementInfoRequest, postUserInfoRequest) 사용 확인했습니다.
31-35: 글로벌 캐시 초기화 도입 전 영향 범위 검토레포에는 userQueries 외에 matchQueries, gameQueries 등 다양한 도메인 캐시가 존재합니다. queryClient.clear()를 적용하면 이들 캐시도 모두 초기화되어 성능 저하나 의도치 않은 동작이 발생할 수 있으므로, 글로벌 초기화가 적합한지 확인하세요. 유지해야 할 캐시가 없다면 전역 초기화를, 특정 도메인만 초기화해야 한다면 기존 cancelQueries/removeQueries 방식을 유지하거나 선택적 제거 로직을 고려해야 합니다.
- await queryClient.cancelQueries({ queryKey: USER_KEY.ALL }); - queryClient.removeQueries({ queryKey: USER_KEY.ALL }); + await queryClient.cancelQueries(); // 전역 취소 + queryClient.clear(); // 전역 캐시 초기화
| const [mateTeam, setMateTeam] = useState(mockEditData.teamAllowed || '상관없어요'); | ||
| const [viewStyle, setViewStyle] = useState(mockEditData.style); | ||
| const [isSubmit, setIsSubmit] = useState(false); |
There was a problem hiding this comment.
초기값 불일치로 인한 상시 Dirty 상태 발생 가능
state는 mateTeam을 mockEditData.teamAllowed || '상관없어요'로 초기화하지만, initialValue는 mockEditData.teamAllowed만 저장합니다. teamAllowed가 undefined일 때 즉시 dirty로 평가됩니다. 둘을 일치시켜 주세요.
const initialValue = useRef({
team: mockEditData.team,
gender: mockEditData.genderPreference,
- mateTeam: mockEditData.teamAllowed,
+ mateTeam: mockEditData.teamAllowed || '상관없어요',
viewStyle: mockEditData.style,
});Also applies to: 25-30, 32-41
🤖 Prompt for AI Agents
In src/pages/edit-profile/edit-profile.tsx around lines 21-23 (and similarly
lines 25-30, 32-41), component state initializers use fallbacks (e.g., mateTeam
= mockEditData.teamAllowed || '상관없어요') while the form initialValue(s) use
mockEditData.* directly, causing fields to be dirty when the data is undefined;
fix by making the form initialValues and corresponding useState initializers
consistent — either apply the same fallback in the form initialValues (e.g., use
mockEditData.teamAllowed || '상관없어요') or remove the fallback from useState and
initialize both from a single normalized source (compute normalizedInitial = {
teamAllowed: mockEditData.teamAllowed || '상관없어요', ... } and use it for both
state and form initialValues).
| const handleSaveClick = () => { | ||
| if (!isDirty) return; | ||
|
|
||
| setIsSubmit(true); | ||
|
|
||
| // TODO: 실제 API 호출 | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
isSubmit 플래그가 해제되지 않아 버튼이 영구 비활성화될 수 있음
API 연동 전이라도 finally에서 해제해 두는 것이 안전합니다.
const handleSaveClick = () => {
if (!isDirty) return;
setIsSubmit(true);
- // TODO: 실제 API 호출
+ // TODO: 실제 API 호출
+ try {
+ // await apiCall();
+ } finally {
+ setIsSubmit(false);
+ }
};Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/pages/edit-profile/edit-profile.tsx around lines 45 to 51, the handler
sets isSubmit but never resets it, which can permanently disable the button;
make handleSaveClick async and wrap the API call (or placeholder) in a
try/catch/finally block, keep setIsSubmit(true) at the start and ensure
setIsSubmit(false) is called in finally so the flag is always cleared even if
the API is not yet implemented or an error occurs.
| import queryClient from '@libs/query-client'; | ||
| import { router } from '@routes/router'; | ||
| import { ROUTES } from '@routes/routes-config'; |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
API 레이어에서 라우터 의존성 분리 제안
공유 mutation 내부에서 라우팅이 일어나면 재사용 시(예: 프로필 수정) 의도치 않은 홈 리다이렉트가 발생할 수 있습니다. 네비게이션은 호출 컴포넌트(가입 플로우)로 올리고, 여기서는 캐시 무효화까지만 수행하는 편이 안전합니다.
원하시면 가입 전용 훅(useSignupUserInfoMutation)으로 분리하는 패치도 드리겠습니다.
적용 예시:
- import { router } from '@routes/router';
- import { ROUTES } from '@routes/routes-config';
...
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: USER_KEY.ALL });
- router.navigate(ROUTES.HOME, { replace: true });
},사용처 점검 스크립트:
Also applies to: 17-21
🏁 Script executed:
#!/bin/bash
# USER_INFO mutation 사용처가 가입 화면 외에 있는지 점검
rg -nP --type=ts 'userMutations\.USER_INFO\(\)' -C2Length of output: 520
API 레이어에서 네비게이션 로직 제거
src/shared/apis/user/user-mutations.ts에서 router/ROUTES import와 router.navigate 호출을 삭제하고 캐시 무효화만 수행하도록 변경하세요. 네비게이션은 src/pages/sign-up/components/signup-step.tsx의 useMutation onSuccess 콜백으로 이동해야 합니다.
// src/shared/apis/user/user-mutations.ts
- import { router } from '@routes/router';
- import { ROUTES } from '@routes/routes-config';
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: USER_KEY.ALL });
- router.navigate(ROUTES.HOME, { replace: true });
},// src/pages/sign-up/components/signup-step.tsx
const userInfoMutation = useMutation(userMutations.USER_INFO(), {
+ onSuccess: () => router.navigate(ROUTES.HOME, { replace: true }),
});📝 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.
| import queryClient from '@libs/query-client'; | |
| import { router } from '@routes/router'; | |
| import { ROUTES } from '@routes/routes-config'; | |
| import queryClient from '@libs/query-client'; |
🤖 Prompt for AI Agents
In src/shared/apis/user/user-mutations.ts around lines 4 to 6, remove the router
and ROUTES imports and delete any router.navigate calls so the module only
performs cache invalidation (e.g., using queryClient.invalidateQueries or
queryClient.refetchQueries) on mutation success; keep the queryClient import and
ensure the mutation success handlers only handle cache updates/invalidations.
Then move the navigation responsibility to the caller: implement
router.navigate(ROUTES.SOME_ROUTE) inside the useMutation onSuccess callback in
src/pages/sign-up/components/signup-step.tsx instead of doing navigation here.
#️⃣ Related Issue
Closes #323
☀️ New-insight
💎 PR Point
agreementInfoMutation.mutateAsync({ hasAccepted: true })호출을 통해사용자가 서비스 약관에 동의했음을 서버에 먼저 기록합니다.
userInfoMutation.mutateAsync(userData)를 호출합니다.📸 Screenshot
테스트는 서버에서 제 유저 정보 DB 날려주면 해보고 추가할게용
Summary by CodeRabbit
신규 기능
개선 사항
기타