-
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
Changes from all commits
3139c4d
9f3221c
ba30ecc
8e7820f
88bfa27
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,26 +1,37 @@ | ||
| import { AnyFieldApi } from '@tanstack/react-form'; | ||
|
|
||
| import { Input, Label } from '@/components/ui'; | ||
| import { Hint, Input, Label } from '@/components/ui'; | ||
|
|
||
| interface Props { | ||
| field: AnyFieldApi; | ||
| } | ||
|
|
||
| export const MBTIField = ({ field }: Props) => { | ||
| const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid; | ||
| const fieldId = 'profile-mbti'; | ||
| return ( | ||
| <div className='mt-3 flex w-full flex-col gap-1'> | ||
| <Label htmlFor='post-meetup-title' required> | ||
| MBTI | ||
| </Label> | ||
|
|
||
| <Label htmlFor={fieldId}>MBTI</Label> | ||
| <Input | ||
| id={fieldId} | ||
| className='bg-mono-white focus:border-mint-500 rounded-2xl border border-gray-300' | ||
| maxLength={4} | ||
| placeholder='MBTI를 입력해주세요' | ||
| required | ||
| type='text' | ||
| value={field.state.value} | ||
| onChange={(e) => field.handleChange(e.target.value)} | ||
| onBlur={field.handleBlur} | ||
| onChange={(e) => { | ||
| field.handleChange(e.target.value); | ||
| field.setMeta((prev) => ({ | ||
| ...prev, | ||
| errorMap: { | ||
| ...prev.errorMap, | ||
| onBlur: undefined, | ||
| }, | ||
| })); | ||
| }} | ||
| /> | ||
| {isInvalid && <Hint message={field.state.meta.errors[0].message} />} | ||
| </div> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,26 +1,26 @@ | ||
| import { AnyFieldApi } from '@tanstack/react-form'; | ||
|
|
||
| import { Input, Label } from '@/components/ui'; | ||
| import { Hint, Input, Label } from '@/components/ui'; | ||
|
|
||
| interface Props { | ||
| field: AnyFieldApi; | ||
| } | ||
|
|
||
| export const MessageField = ({ field }: Props) => { | ||
| const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid; | ||
| const fieldId = 'profile-message'; | ||
| return ( | ||
| <div className='mt-3 flex w-full flex-col gap-1'> | ||
| <Label htmlFor='post-meetup-title' required> | ||
| 한 줄 소개 | ||
| </Label> | ||
|
|
||
| <Label htmlFor={fieldId}>한 줄 소개</Label> | ||
| <Input | ||
| id={fieldId} | ||
| className='bg-mono-white focus:border-mint-500 rounded-2xl border border-gray-300' | ||
| placeholder='한 줄 소개를 입력해주세요' | ||
| required | ||
| type='text' | ||
| value={field.state.value} | ||
| onChange={(e) => field.handleChange(e.target.value)} | ||
| /> | ||
| {isInvalid && <Hint message={field.state.meta.errors[0].message} />} | ||
| </div> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,26 +1,29 @@ | ||
| import { AnyFieldApi } from '@tanstack/react-form'; | ||
|
|
||
| import { Input, Label } from '@/components/ui'; | ||
| import { Hint, Input, Label } from '@/components/ui'; | ||
|
|
||
| interface Props { | ||
| field: AnyFieldApi; | ||
| } | ||
|
|
||
| export const NickNameField = ({ field }: Props) => { | ||
| const isInvalid = !field.state.meta.isValid; | ||
| const fieldId = 'profile-nickname'; | ||
| return ( | ||
| <div className='flex w-full flex-col gap-1'> | ||
| <Label htmlFor='post-meetup-title' required> | ||
| <Label htmlFor={fieldId} required> | ||
| 닉네임 | ||
| </Label> | ||
|
|
||
| <Input | ||
| id={fieldId} | ||
| className='bg-mono-white focus:border-mint-500 rounded-2xl border border-gray-300' | ||
| placeholder='닉네임을 입력해주세요' | ||
| required | ||
| type='text' | ||
| value={field.state.value} | ||
| onChange={(e) => field.handleChange(e.target.value)} | ||
| /> | ||
| {isInvalid && <Hint message={field.state.meta.errors[0].message} />} | ||
| </div> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| export const IMAGE_CONFIG = { | ||
| maxSizeBytes: 20971520, // 20MB | ||
| maxWidth: 2000, | ||
| maxHeight: 2000, | ||
| allowedTypes: ['image/jpeg', 'image/png', 'image/webp'], | ||
| allowedExtensions: ['.jpg', '.jpeg', '.png', '.webp'], | ||
| }; | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| import { z } from 'zod'; | ||
|
|
||
| export const profileImageOnChangeSchema = z.union([ | ||
| z.object({ blobUrl: z.instanceof(File) }), | ||
| z.object({ Url: z.null() }), | ||
| z.object({}), | ||
| ]); | ||
|
Comment on lines
+3
to
+7
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
그러나 현재 스키마는 고정된 키 이름( 🤖 Prompt for AI Agents |
||
|
|
||
| export const nickNameOnChangeSchema = z | ||
| .string() | ||
| .min(2, '닉네임은 2글자 이상이어야 합니다.') | ||
| .max(20, '닉네임은 20글자 이하여야 합니다.'); | ||
|
|
||
| export const profileMessageOnChangeSchema = z | ||
| .string() | ||
| .max(20, '소개글은 20글자까지 작성 가능합니다.'); | ||
|
|
||
| export const mbtiOnChangeSchema = z.string().refine( | ||
| (val) => { | ||
| if (val === '') return true; | ||
| if (val.length >= 1 && !['I', 'E', 'i', 'e'].includes(val[0])) return false; | ||
| if (val.length >= 2 && !['S', 'N', 's', 'n'].includes(val[1])) return false; | ||
| if (val.length >= 3 && !['T', 'F', 't', 'f'].includes(val[2])) return false; | ||
| if (val.length === 4 && !['J', 'P', 'j', 'p'].includes(val[3])) return false; | ||
| return true; | ||
| }, | ||
| { message: '유효한 MBTI가 아닙니다' }, | ||
| ); | ||
|
|
||
| export const mbtiOnBlurSchema = z.string().refine( | ||
| (val) => { | ||
| if (val === '') return true; | ||
| return val.length === 4 && /^[IEie][SNsn][TFtf][JPjp]$/.test(val); | ||
| }, | ||
| { message: 'MBTI 4글자를 모두 입력해주세요' }, | ||
| ); | ||
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에initialImagesprop 누락관련 코드 스니펫(
src/components/pages/user/profile/profile-edit-fields/image-field/index.tsx)에서ImageField는initialImagesprop을 받도록 정의되어 있습니다. 현재 프로필 이미지 URL을 전달하지 않으면 초기 이미지가 표시되지 않을 수 있습니다.🔎 권장 수정안
<form.Field validators={{ onChange: profileImageOnChangeSchema }} - children={(field) => <ImageField field={field} />} + children={(field) => <ImageField field={field} initialImages={[image]} />} name='profileImage' />🤖 Prompt for AI Agents