-
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 3 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,36 @@ | ||
| 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; | ||
| return ( | ||
| <div className='mt-3 flex w-full flex-col gap-1'> | ||
| <Label htmlFor='post-meetup-title' required> | ||
| MBTI | ||
| </Label> | ||
| <Label htmlFor='post-meetup-title'>MBTI</Label> | ||
|
|
||
| <Input | ||
| 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,25 @@ | ||
| 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; | ||
| return ( | ||
| <div className='mt-3 flex w-full flex-col gap-1'> | ||
| <Label htmlFor='post-meetup-title' required> | ||
| 한 줄 소개 | ||
| </Label> | ||
| <Label htmlFor='post-meetup-title'>한 줄 소개</Label> | ||
|
|
||
| <Input | ||
| 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)} | ||
| /> | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| {isInvalid && <Hint message={field.state.meta.errors[0].message} />} | ||
| </div> | ||
| ); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,7 @@ | ||||||||||||||||||||||||||||||||||||
| 'use client'; | ||||||||||||||||||||||||||||||||||||
| import { useForm } from '@tanstack/react-form'; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| import { API } from '@/api'; | ||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||
| Button, | ||||||||||||||||||||||||||||||||||||
| ImageRecord, | ||||||||||||||||||||||||||||||||||||
|
|
@@ -11,6 +12,13 @@ import { | |||||||||||||||||||||||||||||||||||
| } from '@/components/ui'; | ||||||||||||||||||||||||||||||||||||
| import { useUpdateUser } from '@/hooks/use-user'; | ||||||||||||||||||||||||||||||||||||
| import { useUserImageUpdate } from '@/hooks/use-user/use-user-image-update'; | ||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||
| mbtiOnBlurSchema, | ||||||||||||||||||||||||||||||||||||
| mbtiOnChangeSchema, | ||||||||||||||||||||||||||||||||||||
| nickNameOnChangeSchema, | ||||||||||||||||||||||||||||||||||||
| profileImageOnChangeSchema, | ||||||||||||||||||||||||||||||||||||
| profileMessageOnChangeSchema, | ||||||||||||||||||||||||||||||||||||
| } from '@/lib/schema/mypage'; | ||||||||||||||||||||||||||||||||||||
| import { UpdateMyInfoPayloads, User } from '@/types/service/user'; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| import { ImageField, MBTIField, MessageField, NickNameField } from '../profile-edit-fields'; | ||||||||||||||||||||||||||||||||||||
|
|
@@ -24,16 +32,8 @@ export const ProfileEditModal = ({ user }: Props) => { | |||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const { close } = useModal(); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const { | ||||||||||||||||||||||||||||||||||||
| mutateAsync: updateUser, | ||||||||||||||||||||||||||||||||||||
| isPending: isUserInfoPending, | ||||||||||||||||||||||||||||||||||||
| error: _userInfoError, | ||||||||||||||||||||||||||||||||||||
| } = useUpdateUser(); | ||||||||||||||||||||||||||||||||||||
| const { | ||||||||||||||||||||||||||||||||||||
| mutateAsync: updateUserImage, | ||||||||||||||||||||||||||||||||||||
| isPending: isUserImagePending, | ||||||||||||||||||||||||||||||||||||
| error: _userImageError, | ||||||||||||||||||||||||||||||||||||
| } = useUserImageUpdate(); | ||||||||||||||||||||||||||||||||||||
| const { mutateAsync: updateUser, error: userInfoError } = useUpdateUser(); | ||||||||||||||||||||||||||||||||||||
| const { mutateAsync: updateUserImage, error: userImageError } = useUserImageUpdate(); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const form = useForm({ | ||||||||||||||||||||||||||||||||||||
| defaultValues: { | ||||||||||||||||||||||||||||||||||||
|
|
@@ -42,15 +42,30 @@ export const ProfileEditModal = ({ user }: Props) => { | |||||||||||||||||||||||||||||||||||
| profileMessage, | ||||||||||||||||||||||||||||||||||||
| mbti, | ||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||
| validators: { | ||||||||||||||||||||||||||||||||||||
| onSubmitAsync: async ({ value }) => { | ||||||||||||||||||||||||||||||||||||
| if (value.nickName === nickName) return null; | ||||||||||||||||||||||||||||||||||||
| const res = await API.userService.getNicknameAvailability({ nickName: value.nickName }); | ||||||||||||||||||||||||||||||||||||
| if (!res.available) { | ||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||
| form: '입력값을 확인해주세요', | ||||||||||||||||||||||||||||||||||||
| fields: { | ||||||||||||||||||||||||||||||||||||
| nickName: { message: '이미 사용 중인 닉네임입니다' }, | ||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| onSubmit: async ({ value }) => { | ||||||||||||||||||||||||||||||||||||
| const { profileImage, nickName, profileMessage, mbti } = value; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const nextMbti = mbti.toUpperCase(); | ||||||||||||||||||||||||||||||||||||
| // 프로필 항목 업데이트 조건 체크 | ||||||||||||||||||||||||||||||||||||
| const nextProfileInfo: UpdateMyInfoPayloads = { | ||||||||||||||||||||||||||||||||||||
| ...(user.nickName !== value.nickName && { nickName }), | ||||||||||||||||||||||||||||||||||||
| ...(user.profileMessage !== value.profileMessage && { profileMessage }), | ||||||||||||||||||||||||||||||||||||
| ...(user.mbti !== value.mbti && { mbti }), | ||||||||||||||||||||||||||||||||||||
| ...(user.nickName !== nickName && { nickName }), | ||||||||||||||||||||||||||||||||||||
| ...(user.profileMessage !== profileMessage && { profileMessage }), | ||||||||||||||||||||||||||||||||||||
| ...(user.mbti !== nextMbti && { mbti: nextMbti }), | ||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| /* | ||||||||||||||||||||||||||||||||||||
|
|
@@ -68,17 +83,20 @@ export const ProfileEditModal = ({ user }: Props) => { | |||||||||||||||||||||||||||||||||||
| await updateUserImage({ file: imageFileObject }); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| close(); | ||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||||
| /* | ||||||||||||||||||||||||||||||||||||
| todo: 이미지 변경과 정보 변경 중 하나라도 실패하면 각 항목에 대한 에러메시지 보여줘야함 | ||||||||||||||||||||||||||||||||||||
| 이미지 변경과 정보 변경 중 하나라도 실패하면 각 항목에 대한 에러메시지 alert | ||||||||||||||||||||||||||||||||||||
| todo: toast로 변경 | ||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||
| console.log('요청 실패', error); | ||||||||||||||||||||||||||||||||||||
| const errors = []; | ||||||||||||||||||||||||||||||||||||
| if (userInfoError) errors.push('사용자 정보'); | ||||||||||||||||||||||||||||||||||||
| if (userImageError) errors.push('프로필 이미지'); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| alert(`${errors.join(', ')} 업데이트에 실패했습니다. 다시 시도해주세요`); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
86
to
88
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. 에러 상태 참조 시점 오류 - 항상 빈 배열이 됨
🔎 권장 수정안 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const isPending = isUserInfoPending || isUserImagePending; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||
| <ModalContent className='max-w-82.5'> | ||||||||||||||||||||||||||||||||||||
| <ModalTitle>프로필 수정</ModalTitle> | ||||||||||||||||||||||||||||||||||||
|
|
@@ -92,17 +110,37 @@ export const ProfileEditModal = ({ user }: Props) => { | |||||||||||||||||||||||||||||||||||
| form.handleSubmit(); | ||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||
| <form.Field children={(field) => <ImageField field={field} />} name='profileImage' /> | ||||||||||||||||||||||||||||||||||||
| <form.Field children={(field) => <NickNameField field={field} />} name='nickName' /> | ||||||||||||||||||||||||||||||||||||
| <form.Field children={(field) => <MessageField field={field} />} name='profileMessage' /> | ||||||||||||||||||||||||||||||||||||
| <form.Field children={(field) => <MBTIField field={field} />} name='mbti' /> | ||||||||||||||||||||||||||||||||||||
| <form.Field | ||||||||||||||||||||||||||||||||||||
| validators={{ onChange: profileImageOnChangeSchema }} | ||||||||||||||||||||||||||||||||||||
| children={(field) => <ImageField field={field} />} | ||||||||||||||||||||||||||||||||||||
| name='profileImage' | ||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+105
to
+109
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.
관련 코드 스니펫( 🔎 권장 수정안 <form.Field
validators={{ onChange: profileImageOnChangeSchema }}
- children={(field) => <ImageField field={field} />}
+ children={(field) => <ImageField field={field} initialImages={[image]} />}
name='profileImage'
/>
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
| <form.Field | ||||||||||||||||||||||||||||||||||||
| validators={{ onChange: nickNameOnChangeSchema }} | ||||||||||||||||||||||||||||||||||||
| children={(field) => <NickNameField field={field} />} | ||||||||||||||||||||||||||||||||||||
| name='nickName' | ||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||
| <form.Field | ||||||||||||||||||||||||||||||||||||
| validators={{ onChange: profileMessageOnChangeSchema }} | ||||||||||||||||||||||||||||||||||||
| children={(field) => <MessageField field={field} />} | ||||||||||||||||||||||||||||||||||||
| name='profileMessage' | ||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||
| <form.Field | ||||||||||||||||||||||||||||||||||||
| validators={{ onChange: mbtiOnChangeSchema, onBlur: mbtiOnBlurSchema }} | ||||||||||||||||||||||||||||||||||||
| children={(field) => <MBTIField field={field} />} | ||||||||||||||||||||||||||||||||||||
| name='mbti' | ||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||
| <div className='mt-6 flex gap-2'> | ||||||||||||||||||||||||||||||||||||
| <Button variant='tertiary' onClick={close}> | ||||||||||||||||||||||||||||||||||||
| 취소 | ||||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||||
| <Button disabled={isPending} type='submit'> | ||||||||||||||||||||||||||||||||||||
| {isPending ? '수정 중...' : '수정하기'} | ||||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||||
| <form.Subscribe selector={(state) => [state.canSubmit, state.isSubmitting]}> | ||||||||||||||||||||||||||||||||||||
| {([canSubmit, isSubmitting]) => ( | ||||||||||||||||||||||||||||||||||||
| <Button disabled={!canSubmit} type='submit'> | ||||||||||||||||||||||||||||||||||||
| {isSubmitting ? '수정 중...' : '수정하기'} | ||||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||
| </form.Subscribe> | ||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||
| </form> | ||||||||||||||||||||||||||||||||||||
| </ModalContent> | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| 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글자를 모두 입력해주세요' }, | ||
| ); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import { IMAGE_CONFIG } from './constants/image'; | ||
|
|
||
| export const validateImage = (file: File): { valid: boolean; error?: string } => { | ||
| // 1. 파일 크기 검증 | ||
| if (file.size > IMAGE_CONFIG.maxSizeBytes) { | ||
| const currentSizeMB = (file.size / (1024 * 1024)).toFixed(0); | ||
| return { | ||
| valid: false, | ||
| error: `이미지 크기가 너무 큽니다. 최대 20MB까지 가능합니다. \n(현재: ${currentSizeMB}MB)`, | ||
| }; | ||
| } | ||
|
|
||
| // 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})`, | ||
| }; | ||
| } | ||
|
||
|
|
||
| // 3. 확장자 검증 | ||
| const fileName = file.name.toLowerCase(); | ||
| const hasValidExtension = IMAGE_CONFIG.allowedExtensions.some((ext) => fileName.endsWith(ext)); | ||
|
|
||
| if (!hasValidExtension) { | ||
| return { | ||
| valid: false, | ||
| error: `파일 확장자가 올바르지 않습니다. \n(${IMAGE_CONFIG.allowedExtensions.join(', ')}만 가능)`, | ||
| }; | ||
| } | ||
|
|
||
| return { valid: true }; | ||
| }; | ||
Uh oh!
There was an error while loading. Please reload this page.