-
Notifications
You must be signed in to change notification settings - Fork 0
[Feat] 마이페이지 - 프로필 수정 Form 유효성 검사 추가 #216
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Walkthrough프로필 편집 폼에 필드별 Hint 오류 표시를 추가하고, zod 기반 유효성 스키마와 이미지 검증 유틸을 도입했으며, 닉네임 중복 확인 및 MBTI 대문자 정규화와 제출 흐름(조건별 업데이트 및 통합 오류 처리)을 적용했습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User as 사용자
participant Form as 프로필 편집 폼
participant Validator as zod 검증기 (lib/schema/mypage)
participant API as 서버 (닉네임 확인 / 업데이트)
participant ImageUtil as validateImage
participant Modal as 모달 UI
User->>Form: 필드 입력 (onChange)
Form->>Validator: onChange 스키마 검증
Validator-->>Form: 필드 유효성 결과
Form->>Modal: isInvalid 상태 반영 -> Hint 렌더링
User->>Form: MBTI 입력 후 onBlur
Form->>Validator: onBlur 스키마 검증 (4자 엄격)
Validator-->>Form: 검증 결과
User->>Form: 이미지 파일 선택
Form->>ImageUtil: validateImage(file)
ImageUtil-->>Form: 검증 성공/실패
alt 실패
Form->>Modal: alert 및 입력 초기화
else 성공
Form->>Form: 이미지 임시 반영
end
User->>Form: 제출 클릭
Form->>API: 닉네임 중복 확인
API-->>Form: 중복 여부
alt 닉네임 중복
Form->>Modal: 폼 수준 오류 바인딩 (nickName) 표시
else 사용 가능
Form->>API: 변경된 필드만 포함해 updateUser 호출
Form->>API: (변경 시) updateUserImage 호출
API-->>Form: 성공/실패
Form->>Modal: 성공/오류 알림 처리
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (5 passed)
✨ 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 |
🎭 Playwright Report✨ E2E Test가 성공적으로 완료되었습니다. Test 요약 내용을 확인해주세요.
📊 Test Summary
📜 Test Details✅ Passed Tests (9)
|
🎨 Storybook Report✅ 변경 사항이 없습니다 모든 Story가 이전 빌드와 동일합니다.
|
📊 Coverage Report
📉 #216을 main에 병합하면 coverage가 Coverage 요약@@ Coverage Diff @@
## main #216 +/- ##
===========================================
- Coverage 33.66% 33.61% -0.05%
===========================================
Files 155 158 +3
Lines 6555 6712 +157
Branches 250 250 0
===========================================
+ Hits 2207 2256 +49
+ Misses 4348 4456 +108 영향받은 파일
|
🚀 PR Preview Report✨ Build가 성공적으로 완료되었습니다. Preview에서 변경사항을 확인하세요.
|
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this 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
🧹 Nitpick comments (3)
src/components/pages/user/profile/profile-edit-fields/nickname-field/index.tsx (1)
10-10:isTouched체크 누락으로 인한 UX 이슈
isInvalid조건에isTouched체크가 없어서, 사용자가 필드와 상호작용하기 전에도 에러 메시지가 표시될 수 있습니다. 다른 필드 컴포넌트들과 일관성을 위해isTouched체크를 추가하는 것이 좋습니다.🔎 권장 수정안
- const isInvalid = !field.state.meta.isValid; + const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid;Based on learnings, TanStack Form 필드에서는
isTouched && !isValid패턴을 사용하는 것이 권장됩니다.src/lib/validateImage.ts (1)
4-11: 에러 메시지에서 최대 크기를 동적으로 계산하는 것을 권장합니다.현재 에러 메시지에 "최대 20MB"가 하드코딩되어 있습니다.
IMAGE_CONFIG.maxSizeBytes값이 변경될 경우 에러 메시지와 불일치가 발생할 수 있습니다.🔎 동적 최대 크기 계산 제안
// 1. 파일 크기 검증 if (file.size > IMAGE_CONFIG.maxSizeBytes) { const currentSizeMB = (file.size / (1024 * 1024)).toFixed(0); + const maxSizeMB = (IMAGE_CONFIG.maxSizeBytes / (1024 * 1024)).toFixed(0); return { valid: false, - error: `이미지 크기가 너무 큽니다. 최대 20MB까지 가능합니다. \n(현재: ${currentSizeMB}MB)`, + error: `이미지 크기가 너무 큽니다. 최대 ${maxSizeMB}MB까지 가능합니다. \n(현재: ${currentSizeMB}MB)`, }; }src/components/pages/user/profile/profile-edit-fields/mbti-field/index.tsx (1)
15-32: 접근성 향상을 위해 Input에 id 추가를 권장합니다.Label의
htmlFor와 매칭되는id속성을 Input에 추가하면 스크린 리더 지원이 개선됩니다.🔎 제안된 수정 사항
<Input + id='profile-mbti' className='bg-mono-white focus:border-mint-500 rounded-2xl border border-gray-300' maxLength={4} placeholder='MBTI를 입력해주세요' type='text' value={field.state.value} onBlur={field.handleBlur} onChange={(e) => { field.handleChange(e.target.value); field.setMeta((prev) => ({ ...prev, errorMap: { ...prev.errorMap, onBlur: undefined, }, })); }} />
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
src/components/pages/user/profile/profile-edit-fields/mbti-field/index.tsx(1 hunks)src/components/pages/user/profile/profile-edit-fields/message-field/index.tsx(1 hunks)src/components/pages/user/profile/profile-edit-fields/nickname-field/index.tsx(2 hunks)src/components/pages/user/profile/profile-edit-modal/index.tsx(6 hunks)src/components/ui/imageinput/index.tsx(2 hunks)src/lib/constants/image.ts(1 hunks)src/lib/schema/mypage.ts(1 hunks)src/lib/validateImage.ts(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-17T09:40:37.733Z
Learnt from: HopeFullee
Repo: WeGo-Together/WeGo_FrontEnd PR: 205
File: src/components/pages/post-meetup/fields/cap-field/index.tsx:47-47
Timestamp: 2025-12-17T09:40:37.733Z
Learning: In TanStack Form with Zod validation, when field.state.meta.isValid is false, field.state.meta.errors is guaranteed to have at least one entry. Therefore, in an isInvalid check defined as field.state.meta.isTouched && !field.state.meta.isValid, safely access field.state.meta.errors[0] without extra null/empty checks. This guideline applies to TSX form fields under src/components/pages (and similar forms using the same pattern).
Applied to files:
src/components/pages/user/profile/profile-edit-fields/nickname-field/index.tsxsrc/components/pages/user/profile/profile-edit-modal/index.tsxsrc/components/pages/user/profile/profile-edit-fields/message-field/index.tsxsrc/components/pages/user/profile/profile-edit-fields/mbti-field/index.tsx
🧬 Code graph analysis (3)
src/components/pages/user/profile/profile-edit-modal/index.tsx (6)
src/hooks/use-user/use-user-update/index.ts (1)
useUpdateUser(7-17)src/hooks/use-user/use-user-image-update/index.ts (1)
useUserImageUpdate(7-17)src/api/index.ts (1)
API(22-22)src/types/service/user.ts (1)
UpdateMyInfoPayloads(21-25)src/lib/schema/mypage.ts (5)
profileImageOnChangeSchema(3-7)nickNameOnChangeSchema(9-12)profileMessageOnChangeSchema(14-16)mbtiOnChangeSchema(18-28)mbtiOnBlurSchema(30-36)src/components/pages/user/profile/profile-edit-fields/image-field/index.tsx (1)
ImageField(12-55)
src/lib/validateImage.ts (1)
src/lib/constants/image.ts (1)
IMAGE_CONFIG(1-7)
src/components/ui/imageinput/index.tsx (1)
src/lib/validateImage.ts (1)
validateImage(3-34)
🪛 Biome (2.1.2)
src/components/pages/user/profile/profile-edit-modal/index.tsx
[error] 125-125: Avoid passing children using a prop
The canonical way to pass children in React is to use JSX elements
(lint/correctness/noChildrenProp)
[error] 129-129: Avoid passing children using a prop
The canonical way to pass children in React is to use JSX elements
(lint/correctness/noChildrenProp)
[error] 134-134: Avoid passing children using a prop
The canonical way to pass children in React is to use JSX elements
(lint/correctness/noChildrenProp)
[error] 139-139: Avoid passing children using a prop
The canonical way to pass children in React is to use JSX elements
(lint/correctness/noChildrenProp)
🔇 Additional comments (9)
src/components/pages/user/profile/profile-edit-fields/message-field/index.tsx (1)
10-10: 유효성 검사 패턴이 올바르게 적용되었습니다.
isInvalid계산과Hint조건부 렌더링이 프로젝트의 확립된 패턴을 따르고 있으며,isInvalid가true일 때errors[0]이 보장되므로 안전하게 접근할 수 있습니다.Based on learnings, this pattern is safe and consistent with other form fields in the codebase.
Also applies to: 22-22
src/lib/schema/mypage.ts (1)
18-35: MBTI 검증 스키마 구현이 적절함
mbtiOnChangeSchema는 입력 중 부분 검증을,mbtiOnBlurSchema는 완료 시 전체 검증을 수행하여 요구사항에 맞게 구현되었습니다. 대소문자 모두 허용하고 빈 값도 유효하게 처리합니다.src/components/pages/user/profile/profile-edit-modal/index.tsx (1)
113-132:childrenprop 사용은 TanStack Form의 권장 패턴Biome에서
childrenprop 사용에 대한 경고가 있지만, 이는@tanstack/react-form의form.Field컴포넌트에서 render props 패턴으로 권장되는 사용법입니다. 이 경고는 무시해도 됩니다.src/components/ui/imageinput/index.tsx (1)
109-117: 이미지 검증 로직이 적절하게 구현됨파일 선택 시 검증을 수행하고, 실패 시 즉시 사용자에게 피드백을 제공합니다.
maxFiles=1인 프로필 이미지 사용 케이스에 적합합니다.
alert대신toast로 변경하는 TODO가 있는데, 일관된 UX를 위해 추후 개선이 필요합니다.src/lib/validateImage.ts (2)
1-3: LGTM!import 구문과 함수 시그니처가 적절합니다. 반환 타입
{ valid: boolean; error?: string }이 유효성 검사 결과를 명확하게 표현합니다.
22-34: 확장자 검증 로직이 정확합니다.대소문자 구분 없이 확장자를 검증하는 로직이 올바르게 구현되었습니다.
다만
IMAGE_CONFIG에 정의된maxWidth와maxHeight는 현재 이 함수에서뿐만 아니라 전체 코드베이스에서 사용되지 않고 있습니다. 이미지 크기 검증이 필요한 경우 구현이 필요하고, 불필요하다면 설정에서 제거하는 것을 고려해 주세요.Likely an incorrect or invalid review comment.
src/components/pages/user/profile/profile-edit-fields/mbti-field/index.tsx (3)
3-3: LGTM! 에러 표시 패턴이 올바릅니다.
Hint컴포넌트 import 및 조건부 에러 표시 로직이 TanStack Form 패턴을 올바르게 따르고 있습니다. Learning에 따르면isValid가 false일 때errors[0]는 보장되므로 안전하게 접근할 수 있습니다.Also applies to: 10-10, 33-33
17-17: LGTM! maxLength 속성이 적절합니다.MBTI는 정확히 4글자이므로
maxLength={4}는 적절한 제약입니다.
21-31: onChange/onBlur 핸들러 패턴 및 유효성 검사 스키마 검증 완료코드 검증 결과, 모든 PR 요구사항이 올바르게 구현되었습니다:
- onChange 검증:
mbtiOnChangeSchema로 부분 입력 허용 (1-4글자), 대소문자 구분 없이 각 자리수별 유효성 검사- onBlur 검증:
mbtiOnBlurSchema로 완전한 4글자 MBTI 검증, 패턴/^[IEie][SNsn][TFtf][JPjp]$/는 16가지 유효한 조합 모두 포함- 대문자 변환:
ProfileEditModal라인 63에서 제출 시.toUpperCase()처리- 에러 관리: onChange에서 onBlur 에러 제거하여 부분 입력 중 에러 미표시 ✓
검증 패턴이 모든 E/I, S/N, T/F, J/P 조합(총 16가지)을 정확히 지원하므로 추가 수정 불필요합니다.
src/components/pages/user/profile/profile-edit-fields/mbti-field/index.tsx
Outdated
Show resolved
Hide resolved
src/components/pages/user/profile/profile-edit-fields/message-field/index.tsx
Outdated
Show resolved
Hide resolved
src/components/pages/user/profile/profile-edit-fields/nickname-field/index.tsx
Outdated
Show resolved
Hide resolved
| } catch { | ||
| /* | ||
| todo: 이미지 변경과 정보 변경 중 하나라도 실패하면 각 항목에 대한 에러메시지 보여줘야함 | ||
| 이미지 변경과 정보 변경 중 하나라도 실패하면 각 항목에 대한 에러메시지 alert | ||
| todo: toast로 변경 | ||
| */ | ||
| console.log('요청 실패', error); | ||
| const errors = []; | ||
| if (userInfoError) errors.push('사용자 정보'); | ||
| if (userImageError) errors.push('프로필 이미지'); | ||
|
|
||
| alert(`${errors.join(', ')} 업데이트에 실패했습니다. 다시 시도해주세요`); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
에러 상태 참조 시점 오류 - 항상 빈 배열이 됨
userInfoError와 userImageError는 컴포넌트 렌더링 시점의 값을 클로저로 캡처합니다. mutateAsync가 실패해도 catch 블록 내에서는 아직 이전 상태를 참조하므로, errors 배열은 항상 빈 상태가 됩니다.
🔎 권장 수정안
try {
if (Object.keys(nextProfileInfo).length > 0) {
await updateUser(nextProfileInfo);
}
const imageFileObject = Object.values(profileImage)[0];
if (imageFileObject) {
await updateUserImage({ file: imageFileObject });
}
close();
- } catch {
- const errors = [];
- if (userInfoError) errors.push('사용자 정보');
- if (userImageError) errors.push('프로필 이미지');
-
- alert(`${errors.join(', ')} 업데이트에 실패했습니다. 다시 시도해주세요`);
+ } catch (error) {
+ // mutateAsync는 실패 시 에러를 throw하므로 직접 처리
+ alert('프로필 업데이트에 실패했습니다. 다시 시도해주세요');
}또는 각 mutation을 개별적으로 try-catch하여 어떤 작업이 실패했는지 추적:
let userUpdateFailed = false;
let imageUpdateFailed = false;
if (Object.keys(nextProfileInfo).length > 0) {
try {
await updateUser(nextProfileInfo);
} catch {
userUpdateFailed = true;
}
}
const imageFileObject = Object.values(profileImage)[0];
if (imageFileObject) {
try {
await updateUserImage({ file: imageFileObject });
} catch {
imageUpdateFailed = true;
}
}
if (userUpdateFailed || imageUpdateFailed) {
const errors = [];
if (userUpdateFailed) errors.push('사용자 정보');
if (imageUpdateFailed) errors.push('프로필 이미지');
alert(`${errors.join(', ')} 업데이트에 실패했습니다. 다시 시도해주세요`);
} else {
close();
}📝 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.
| } catch { | |
| /* | |
| todo: 이미지 변경과 정보 변경 중 하나라도 실패하면 각 항목에 대한 에러메시지 보여줘야함 | |
| 이미지 변경과 정보 변경 중 하나라도 실패하면 각 항목에 대한 에러메시지 alert | |
| todo: toast로 변경 | |
| */ | |
| console.log('요청 실패', error); | |
| const errors = []; | |
| if (userInfoError) errors.push('사용자 정보'); | |
| if (userImageError) errors.push('프로필 이미지'); | |
| alert(`${errors.join(', ')} 업데이트에 실패했습니다. 다시 시도해주세요`); | |
| } | |
| } catch (error) { | |
| // mutateAsync는 실패 시 에러를 throw하므로 직접 처리 | |
| alert('프로필 업데이트에 실패했습니다. 다시 시도해주세요'); | |
| } |
🤖 Prompt for AI Agents
In src/components/pages/user/profile/profile-edit-modal/index.tsx around lines
86 to 96, the catch block reads userInfoError and userImageError which are stale
values captured by the render closure so the errors array is always empty;
instead, track failure locally by wrapping each mutation in its own try/catch
(or set boolean flags immediately when a mutation throws) then build the errors
array from those flags and show the alert (and call close() only when neither
failed).
| <form.Field | ||
| validators={{ onChange: profileImageOnChangeSchema }} | ||
| children={(field) => <ImageField field={field} />} | ||
| name='profileImage' | ||
| /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ImageField에 initialImages prop 누락
관련 코드 스니펫(src/components/pages/user/profile/profile-edit-fields/image-field/index.tsx)에서 ImageField는 initialImages prop을 받도록 정의되어 있습니다. 현재 프로필 이미지 URL을 전달하지 않으면 초기 이미지가 표시되지 않을 수 있습니다.
🔎 권장 수정안
<form.Field
validators={{ onChange: profileImageOnChangeSchema }}
- children={(field) => <ImageField field={field} />}
+ children={(field) => <ImageField field={field} initialImages={[image]} />}
name='profileImage'
/>Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/components/pages/user/profile/profile-edit-modal/index.tsx around lines
113 to 117, the ImageField component is rendered without the required
initialImages prop so the current profile image won't be displayed; pass a prop
initialImages that supplies the existing profile image URL (e.g. an array
containing the currentProfileImage or form.initialValues.profileImage) to the
ImageField component (ensure you map undefined/null to an empty array) so the
field shows the initial image.
| export const profileImageOnChangeSchema = z.union([ | ||
| z.object({ blobUrl: z.instanceof(File) }), | ||
| z.object({ Url: z.null() }), | ||
| z.object({}), | ||
| ]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for ImageRecord type definition
echo "=== ImageRecord 타입 정의 ==="
rg -n "type ImageRecord|interface ImageRecord" --type ts -A 2
echo -e "\n=== profileImageOnChangeSchema 사용처 ==="
rg -n "profileImageOnChangeSchema" --type ts -A 3
echo -e "\n=== profileImageOnChangeSchema 정의 주변 전체 내용 ==="
cat -n src/lib/schema/mypage.ts | head -40Repository: WeGo-Together/WeGo_FrontEnd
Length of output: 2854
🏁 Script executed:
#!/bin/bash
# Find and examine ImageField component
echo "=== ImageField 컴포넌트 정의 찾기 ==="
rg -n "export.*ImageField|function ImageField" --type tsx -A 20
echo -e "\n=== imageinput/index.tsx 전체 내용 ==="
cat -n src/components/ui/imageinput/index.tsxRepository: WeGo-Together/WeGo_FrontEnd
Length of output: 4769
🏁 Script executed:
#!/bin/bash
# Search for ImageField definition
echo "=== ImageField 컴포넌트 찾기 ==="
rg -n "ImageField|export.*ImageField" --type ts -B 2 -A 15 | head -100
echo -e "\n=== profile-edit-modal 구조 ==="
ls -la src/components/pages/user/profile/profile-edit-modal/
echo -e "\n=== field 관련 코드 확인 ==="
rg -n "class.*Field|export.*Field" src/components/pages/user/profile/profile-edit-modal/ --type ts -A 5Repository: WeGo-Together/WeGo_FrontEnd
Length of output: 6433
🏁 Script executed:
#!/bin/bash
# Get full ImageField implementation
echo "=== ImageField 전체 코드 ==="
cat -n src/components/pages/user/profile/profile-edit-fields/image-field/index.tsx
echo -e "\n=== form.Field 사용 방식 확인 ==="
rg -n "form\.Field" src/components/pages/user/profile/profile-edit-modal/index.tsx -B 2 -A 5 | head -60Repository: WeGo-Together/WeGo_FrontEnd
Length of output: 3337
🏁 Script executed:
#!/bin/bash
# Final confirmation: Check what the validator actually receives
echo "=== Confirm flow: ImageInput onChange -> field.handleChange -> validator ==="
echo "ImageInput.onChange signature:"
rg -n "onChange\?" src/components/ui/imageinput/index.tsx | grep -A2 "ImageInputProps"
echo -e "\n=== Confirm ImageRecord structure with blob URLs ==="
rg -n "URL.createObjectURL" src/components/ui/imageinput/index.tsx -B 2 -A 2Repository: WeGo-Together/WeGo_FrontEnd
Length of output: 390
profileImageOnChangeSchema의 스키마 구조가 실제 데이터 구조와 완전히 불일치하여 검증 실패
ImageInput 컴포넌트가 field.handleChange에 전달하는 데이터는 Record<string, File | null> 형태이며, 키는 URL.createObjectURL()로 생성된 동적인 blob URL입니다 (예: { "blob:http://...": File }).
그러나 현재 스키마는 고정된 키 이름(blobUrl, Url)을 기대하고 있으므로 실제 데이터와 일치하지 않아 모든 검증이 실패합니다. 스키마를 z.record(z.string(), z.union([z.instanceof(File), z.null()])) 형태로 수정하여 동적 키를 허용하도록 변경해야 합니다.
🤖 Prompt for AI Agents
In src/lib/schema/mypage.ts around lines 3 to 7, the profileImageOnChangeSchema
expects fixed property names but the component sends a Record with dynamic
blob-URL keys; replace the current union of fixed objects with a record schema
that allows any string key and values that are either File instances or null
(e.g., z.record(z.string(), z.union([z.instanceof(File), z.null()]))) so
validation matches the actual Record<string, File | null> shape.
src/lib/validateImage.ts
Outdated
| // 2. Content Type 검증 | ||
| if (!IMAGE_CONFIG.allowedTypes.includes(file.type)) { | ||
| const currentFileType = file.type.split('/')[1]; | ||
| return { | ||
| valid: false, | ||
| error: `${IMAGE_CONFIG.allowedExtensions.join(', ')} 형식만 업로드 가능합니다. \n(현재: ${currentFileType})`, | ||
| }; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
file.type이 빈 문자열일 경우 에러 메시지가 불명확해질 수 있습니다.
브라우저가 파일 타입을 결정하지 못하면 file.type이 빈 문자열("")이 될 수 있습니다. 이 경우 file.type.split('/')[1]은 undefined를 반환하여 에러 메시지에 "(현재: undefined)"가 표시됩니다.
🔎 fallback 처리 제안
// 2. Content Type 검증
if (!IMAGE_CONFIG.allowedTypes.includes(file.type)) {
- const currentFileType = file.type.split('/')[1];
+ const currentFileType = file.type ? file.type.split('/')[1] : '알 수 없음';
return {
valid: false,
error: `${IMAGE_CONFIG.allowedExtensions.join(', ')} 형식만 업로드 가능합니다. \n(현재: ${currentFileType})`,
};
}🤖 Prompt for AI Agents
In src/lib/validateImage.ts around lines 13 to 20, file.type can be an empty
string so file.type.split('/')[1] may be undefined and produce "(현재:
undefined)"; update the code to compute a safe currentFileType by first checking
if file.type is a non-empty string containing a slash and using the part after
the slash, otherwise try to fall back to the file extension from file.name (e.g.
last segment after '.') if present, and if neither is available use a clear
placeholder like "알 수 없음" — then use that safe value in the returned error
message.
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/components/pages/user/profile/profile-edit-modal/index.tsx (1)
86-87: 에러 처리가 단순화되었지만, 선택적으로 개선 가능합니다.통합 에러 메시지로 단순화하여 이전 리뷰에서 지적된 stale closure 문제를 해결했습니다. 다만 어떤 업데이트(사용자 정보 vs 프로필 이미지)가 실패했는지에 대한 정보는 손실되었습니다.
사용자에게 더 명확한 피드백을 제공하려면 각 mutation을 개별 try-catch로 감싸서 실패한 항목을 추적할 수 있습니다.
🔎 선택적 개선안: 세밀한 에러 추적
try { + let userUpdateFailed = false; + let imageUpdateFailed = false; + if (Object.keys(nextProfileInfo).length > 0) { - await updateUser(nextProfileInfo); + try { + await updateUser(nextProfileInfo); + } catch { + userUpdateFailed = true; + } } const imageFileObject = Object.values(profileImage)[0]; if (imageFileObject) { - await updateUserImage({ file: imageFileObject }); + try { + await updateUserImage({ file: imageFileObject }); + } catch { + imageUpdateFailed = true; + } } - close(); - } catch { - alert(`업데이트에 실패했습니다. 잠시 후 다시 시도해주세요`); + + if (userUpdateFailed || imageUpdateFailed) { + const errors = []; + if (userUpdateFailed) errors.push('사용자 정보'); + if (imageUpdateFailed) errors.push('프로필 이미지'); + alert(`${errors.join(', ')} 업데이트에 실패했습니다. 다시 시도해주세요`); + } else { + close(); + } + } catch (error) { + // 예상치 못한 에러 처리 + alert(`업데이트에 실패했습니다. 잠시 후 다시 시도해주세요`); }
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
src/components/pages/user/profile/profile-edit-fields/image-field/index.tsx(0 hunks)src/components/pages/user/profile/profile-edit-fields/mbti-field/index.tsx(1 hunks)src/components/pages/user/profile/profile-edit-fields/message-field/index.tsx(1 hunks)src/components/pages/user/profile/profile-edit-fields/nickname-field/index.tsx(1 hunks)src/components/pages/user/profile/profile-edit-modal/index.tsx(6 hunks)src/components/ui/imageinput/index.tsx(3 hunks)src/lib/validateImage.ts(1 hunks)
💤 Files with no reviewable changes (1)
- src/components/pages/user/profile/profile-edit-fields/image-field/index.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- src/components/pages/user/profile/profile-edit-fields/nickname-field/index.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-17T09:40:37.733Z
Learnt from: HopeFullee
Repo: WeGo-Together/WeGo_FrontEnd PR: 205
File: src/components/pages/post-meetup/fields/cap-field/index.tsx:47-47
Timestamp: 2025-12-17T09:40:37.733Z
Learning: In TanStack Form with Zod validation, when field.state.meta.isValid is false, field.state.meta.errors is guaranteed to have at least one entry. Therefore, in an isInvalid check defined as field.state.meta.isTouched && !field.state.meta.isValid, safely access field.state.meta.errors[0] without extra null/empty checks. This guideline applies to TSX form fields under src/components/pages (and similar forms using the same pattern).
Applied to files:
src/components/pages/user/profile/profile-edit-fields/message-field/index.tsxsrc/components/pages/user/profile/profile-edit-modal/index.tsxsrc/components/pages/user/profile/profile-edit-fields/mbti-field/index.tsx
🧬 Code graph analysis (3)
src/components/pages/user/profile/profile-edit-modal/index.tsx (5)
src/hooks/use-user/use-user-update/index.ts (1)
useUpdateUser(7-17)src/hooks/use-user/use-user-image-update/index.ts (1)
useUserImageUpdate(7-17)src/api/index.ts (1)
API(22-22)src/types/service/user.ts (1)
UpdateMyInfoPayloads(21-25)src/lib/schema/mypage.ts (5)
profileImageOnChangeSchema(3-7)nickNameOnChangeSchema(9-12)profileMessageOnChangeSchema(14-16)mbtiOnChangeSchema(18-28)mbtiOnBlurSchema(30-36)
src/lib/validateImage.ts (1)
src/lib/constants/image.ts (1)
IMAGE_CONFIG(1-7)
src/components/ui/imageinput/index.tsx (2)
src/lib/constants/image.ts (1)
IMAGE_CONFIG(1-7)src/lib/validateImage.ts (1)
validateImage(3-34)
🪛 Biome (2.1.2)
src/components/pages/user/profile/profile-edit-modal/index.tsx
[error] 115-116: Avoid passing children using a prop
The canonical way to pass children in React is to use JSX elements
(lint/correctness/noChildrenProp)
[error] 120-120: Avoid passing children using a prop
The canonical way to pass children in React is to use JSX elements
(lint/correctness/noChildrenProp)
[error] 124-125: Avoid passing children using a prop
The canonical way to pass children in React is to use JSX elements
(lint/correctness/noChildrenProp)
[error] 129-129: Avoid passing children using a prop
The canonical way to pass children in React is to use JSX elements
(lint/correctness/noChildrenProp)
⏰ 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). (3)
- GitHub Check: preview-deploy
- GitHub Check: chromatic
- GitHub Check: coverage
🔇 Additional comments (6)
src/components/ui/imageinput/index.tsx (1)
107-123: 이미지 검증 로직이 올바르게 구현되었습니다.각 파일에 대해
validateImage를 호출하여 확장자, 크기, 치수를 검증하고, 검증 실패 시 입력을 초기화하여 잘못된 파일이 추가되지 않도록 방지하는 로직이 잘 구현되었습니다.
alert사용은 코멘트에서 언급된 대로 임시 방편으로 보이며, 향후 toast 알림으로 교체 시 더 나은 사용자 경험을 제공할 수 있습니다.src/components/pages/user/profile/profile-edit-fields/message-field/index.tsx (1)
10-23: 이전 리뷰의 접근성 문제가 해결되었습니다.
Label의htmlFor와Input의id가'profile-message'로 올바르게 연결되었으며, 유효하지 않은 필드에 대한Hint표시도 적절하게 구현되었습니다.src/components/pages/user/profile/profile-edit-fields/mbti-field/index.tsx (1)
10-34: 이전 리뷰의 접근성 문제가 해결되었으며, MBTI 검증 로직이 적절합니다.
Label의htmlFor와Input의id가 올바르게 연결되었고,maxLength={4}제약과onBlur검증이 추가되었습니다.
onChange핸들러에서errorMap.onBlur를 수동으로 제거하는 부분(lines 25-31)은 사용자가 입력 중일 때 onBlur 에러 메시지를 즉시 제거하여 더 나은 UX를 제공하기 위한 의도적인 구현으로 보입니다. 이는 PR 목표에 명시된 요구사항(onChange 시 부분 MBTI 허용, onBlur 시 완전한 MBTI 요구)과 일치합니다.src/components/pages/user/profile/profile-edit-modal/index.tsx (3)
45-59: 닉네임 중복 확인 로직이 잘 구현되었습니다.
onSubmitAsync검증자에서 닉네임이 변경된 경우에만 API를 호출하고(line 47), 사용 불가능한 닉네임에 대해 필드 수준의 에러를 반환하여 사용자에게 명확한 피드백을 제공합니다.
105-124: Biome 경고는 무시해도 됩니다 - TanStack Form의 정상적인 API 사용입니다.Biome가
childrenprop 사용에 대해 경고하고 있지만, 이는 TanStack Form의form.Field컴포넌트가 render function을 받는 공식 API입니다. JSX children이 아닌 함수형 children 패턴으로, 필드 상태를 하위 컴포넌트에 전달하기 위한 의도적인 설계입니다.각 필드에 적절한 검증 스키마(
onChange,onBlur)가 적용되어 있고, 필드별 에러 표시도 잘 구현되었습니다.
129-135: 동적 제출 버튼 구현이 우수합니다.
form.Subscribe를 사용하여canSubmit과isSubmitting상태에 따라 버튼의 활성화 여부와 레이블을 동적으로 변경하는 것은 좋은 사용자 경험을 제공합니다.
| }; | ||
| } | ||
|
|
||
| // 3. 파일 사이즈 검증 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
주석이 검증 내용과 일치하지 않습니다.
Line 24의 주석이 "// 3. 파일 사이즈 검증"으로 되어 있지만, 실제로는 이미지의 너비와 높이(치수)를 검증하고 있습니다. 파일 크기 검증은 이미 lines 15-22에서 수행되었습니다.
🔎 주석 수정 제안
- // 3. 파일 사이즈 검증
+ // 3. 이미지 치수 검증
const { width, height } = await getImageDimensions(file);📝 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.
| // 3. 파일 사이즈 검증 | |
| // 3. 이미지 치수 검증 | |
| const { width, height } = await getImageDimensions(file); |
🤖 Prompt for AI Agents
In src/lib/validateImage.ts around line 24, the comment "// 3. 파일 사이즈 검증" is
incorrect because the code validates image dimensions (width/height), not file
size; update the comment to accurately reflect the check (for example "// 3. 이미지
치수 검증 (너비/높이)") and ensure numbering and language match surrounding comments.
wooktori
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
고생하셨습니다!
📝 변경 사항
마이페이지에서 프로필 수정 Form의 유효성검증 기능을 추가했습니다.
💡 1. 이미지
이미지를 선택할 때 validation이 실행되며 검증을 통과하지 못하면 이미지가 교체되지 않습니다.
이미지 확장자 검증
이미지 크기 검증
이미지 높이, 너비 검증
💡 2. mbti
MBTI 작성중(onChange)
IS)MBTI 작성 완료
onSubmit 시
mbti는 대문자로 변환됩니다.
🔗 관련 이슈
Closes #211
🧪 테스트 방법
📸 스크린샷 (선택)
📋 체크리스트
💬 추가 코멘트
CodeRabbit Review는 자동으로 실행되지 않습니다.
Review를 실행하려면 comment에 아래와 같이 작성해주세요
Summary by CodeRabbit
새로운 기능
개선
버그 수정
✏️ Tip: You can customize this high-level summary in your review settings.