Conversation
Walkthrough문제 → 영향 → 대안 Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UI as Header/Dropdown
participant Form as useProfileForm
participant Query as useUserProfile
participant MutImg as useUpdateProfileImage
participant MutName as useUpdateNickname
participant API as Backend
participant Cache as ReactQueryCache
User->>UI: 아바타 클릭 -> 드롭다운 오픈
UI->>Query: 프로필 요청 (enabled)
Query->>API: fetchCurrentUserProfile()
API-->>Cache: 프로필 응답 캐시
Cache-->>UI: 프로필 데이터 제공
User->>UI: 이미지 선택 / 닉네임 입력
UI->>Form: handleImageChange / setNickname
alt immediate image upload
Form->>MutImg: mutate(file)
MutImg->>API: updateProfileImage(file)
API-->>MutImg: User 응답
MutImg->>Cache: setQueryData(profile)
end
User->>UI: 저장 클릭
UI->>MutName: mutate(nickname)
MutName->>API: updateNickname(nickname)
API-->>MutName: User 응답
MutName->>Cache: invalidateQueries(profile)
Cache-->>UI: 최신 프로필 반영
🎯 Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 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 |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@src/features/user/components/MyPageDropdown.tsx`:
- Around line 29-42: The form's nickname state in useProfileForm isn't updating
when the initialNickname/initialImageUrl props change, causing stale values; fix
this by adding a useEffect inside useProfileForm that watches initialNickname
and initialImageUrl and calls setNickname and sets profileImagePreview (or
equivalent state) when those props change, ensuring the component's nickname and
profileImagePreview sync with incoming user updates; alternatively, as an
option, update MyPageDropdown to only call useProfileForm after user is loaded
(conditional render) to avoid passing empty initial values.
In `@src/features/user/hooks/useOnboarding.ts`:
- Around line 38-41: The build fails because authQueryKeys is referenced but not
defined; fix by either adding an authQueryKeys factory in src/features/auth that
mirrors the userQueryKeys pattern (exporting e.g. authQueryKeys.me() returning
['auth','me']), or by replacing authQueryKeys.me() in useOnboarding (the
Promise.all call that calls queryClient.invalidateQueries) with the same literal
key used in useAuth (['auth','me']); update imports accordingly so
useOnboarding.ts no longer references an undefined symbol.
In `@src/shared/layout/components/Header.tsx`:
- Around line 31-38: Header.tsx references ROUTES.INVITE_BASE when computing
isInvitePage and enabling useUserProfile, but that constant is missing; add a
new ROUTES.INVITE_BASE entry to your ROUTES export (in the routes constants
file) with the invite base path or replace the reference in Header.tsx (the
isInvitePage calculation) with the actual invite path string; ensure the ROUTES
symbol name matches other usages and update imports if needed so isInvitePage
and useUserProfile({ enabled: !isInvitePage || isLoggedIn }) evaluate correctly.
🧹 Nitpick comments (5)
src/pages/Home/HomePage.tsx (1)
4-8: 로딩 상태 처리 고려.
useUserProfile()의isLoading상태를 활용하지 않아, 초기 로딩 시 빈 인사말(안녕하세요, 님!)이 잠깐 노출될 수 있습니다.간단한 스켈레톤이나 조건부 렌더링으로 UX 개선 가능합니다.
💡 선택적 개선안
export default function HomePage() { - const { data: user } = useUserProfile() + const { data: user, isLoading } = useUserProfile() return ( <div className="flex flex-col gap-xtiny pt-xlarge"> - <h1 className="text-black typo-heading2">안녕하세요, {user?.nickname ?? ''}님!</h1> + <h1 className="text-black typo-heading2"> + {isLoading ? '안녕하세요!' : `안녕하세요, ${user?.nickname ?? ''}님!`} + </h1> <p className="text-grey-600 typo-heading2">읽고 있는 책과 생각을 기록해보세요</p> </div> ) }src/features/user/hooks/useUpdateNickname.ts (1)
18-23: 캐시 업데이트 전략 불일치 (참고)
useUpdateProfileImage는setQueryData로 즉시 캐시 갱신하고, 이 훅은invalidateQueries로 refetch를 트리거합니다.updateNicknameAPI도User를 반환하므로setQueryData사용이 가능합니다.현재 구현도 정상 동작하지만, 일관성을 위해 동일한 패턴 적용을 고려해볼 수 있습니다.
♻️ setQueryData 패턴 적용 예시
return useMutation<User, ApiError, string>({ mutationFn: (nickname) => updateNickname(nickname), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: userQueryKeys.profile() }) + onSuccess: (data) => { + queryClient.setQueryData<User>(userQueryKeys.profile(), data) }, })src/features/user/components/ProfileImagePicker.tsx (1)
114-118:showRemoveButton와onRemove간의 암묵적 의존성
showRemoveButton={true}일 때onRemove가undefined면 버튼이 보이지만 클릭해도 아무 동작이 없습니다.의도적 설계라면 무시해도 되지만, 방어적으로 조건을 강화할 수 있습니다.
♻️ 제안: 조건 강화
- {showRemoveButton && ( + {showRemoveButton && onRemove && ( <TextButton onClick={onRemove} disabled={disabled}> 제거하기 </TextButton> )}src/features/user/hooks/useProfileForm.ts (2)
77-96: 닉네임 변경 여부 중복 체크
isNicknameChanged(Line 84)와debouncedNickname !== initialNickname(Line 90) 조건이checkNicknameChange=true일 때 중복됩니다.현재 동작에 문제는 없지만, 조건을 단순화하면 가독성이 향상됩니다.
♻️ 제안: 조건 단순화
const shouldCheckNickname = debouncedNickname.length >= 2 && - isNicknameChanged && - debouncedNickname !== initialNickname && + (!checkNicknameChange || debouncedNickname !== initialNickname) && NICKNAME_REGEX.test(debouncedNickname)
138-157: FileReader 오류 처리 누락
FileReader.onerror가 처리되지 않아 파일 읽기 실패 시 사용자에게 피드백이 없습니다.실제 발생 가능성이 낮지만, 방어적 처리를 권장합니다.
♻️ 제안: 에러 핸들러 추가
const reader = new FileReader() reader.onloadend = () => { setProfileImagePreview(reader.result as string) } + reader.onerror = () => { + console.error('Failed to read file') + setProfileImageFile(null) + } reader.readAsDataURL(file)
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/features/user/hooks/useProfileForm.ts`:
- Around line 103-126: The nicknameStatus memo incorrectly treats nicknameCheck
=== undefined as 'checking' unconditionally; update the branching in
useProfileForm's nicknameStatus so that the 'checking' branch requires
shouldCheckNickname (use shouldCheckNickname && (isCheckingNickname ||
nicknameCheck === undefined)); when shouldCheckNickname is false, decide between
'unchanged' or 'available' according to the checkNicknameChange option and the
relationship between debouncedNickname and initialNickname (so canSubmit isn't
blocked when no duplicate check is needed). Ensure you reference and adjust the
nicknameStatus computation and any logic using nicknameCheck,
isCheckingNickname, shouldCheckNickname, checkNicknameChange, debouncedNickname,
and initialNickname.
🧹 Nitpick comments (2)
src/shared/constants/routes.ts (1)
19-21: INVITE 경로 파라미터 인코딩 필요문제:
invitationCode를 그대로 경로에 삽입해 특수문자(/,?,#, 공백 등) 포함 시 라우팅이 깨질 수 있어요.
영향: 초대 코드에 따라 링크 이동 실패/잘못된 경로 매칭이 발생할 수 있습니다.
대안: 경로 생성 시encodeURIComponent로 안전하게 인코딩하세요.🔧 수정 제안
- INVITE: (invitationCode: string) => `/invite/${invitationCode}`, + INVITE: (invitationCode: string) => + `/invite/${encodeURIComponent(invitationCode)}`,src/features/auth/hooks/index.ts (1)
1-1:useAuth에서authQueryKeysfactory 사용으로 일관성 확보
authQueryKeysfactory를 도입했으나useAuth.tsline 24에서 여전히 하드코딩된['auth', 'me']를 사용 중입니다. 동일한 키 값이지만, query key의 단일 진실 공급원(single source of truth)이 손상되어 나중에 키 구조 변경 시 여러 곳을 수정해야 합니다.✅ 수정 제안
// src/features/auth/hooks/useAuth.ts +import { authQueryKeys } from './authQueryKeys' export function useAuth() { return useQuery({ - queryKey: ['auth', 'me'], + queryKey: authQueryKeys.me(), queryFn: fetchCurrentUser, retry: false, staleTime: 5 * 60 * 1000, }) }
🚀 풀 리퀘스트 제안
📋 작업 내용
Header 컴포넌트의 프로필 아바타 클릭 시 나타나는 마이페이지 드롭다운 구현 및 메인페이지 사용자 닉네임 연동
주요 기능
🔧 변경 사항
Phase 1: API & Hooks
user.endpoints: PROFILE_IMAGE 엔드포인트 추가user.api: updateNickname, updateProfileImage, deleteProfileImage 함수 추가Phase 2: 공통 컴포넌트
ProfileImagePicker: 프로필 이미지 선택 UI (variant: onboarding / mypage)NicknameInput: 닉네임 입력 필드 (검증 상태 표시)useProfileForm: 프로필 폼 상태 관리 hookMAX_IMAGE_SIZE: 이미지 5MB 제한 상수 추가Phase 3: 마이페이지 드롭다운
MyPageDropdown: Header Popover에 연동Phase 4: 리팩토링 & 통합
OnboardingPage: 공통 컴포넌트 적용으로 리팩토링Header: MyPageDropdown Popover 연동Phase 5: 메인페이지
HomePage: useUserProfile 연동, 닉네임 인사말 표시📸 스크린샷
📄 기타
Summary by CodeRabbit
새로운 기능
기타 개선
자산
✏️ Tip: You can customize this high-level summary in your review settings.