Skip to content

Commit 1e7c7b4

Browse files
authored
Merge pull request #124 from youdaeng2/refactor/modal-image
fix:와인 모달 이미지 확장자 유효성 검사 및 잘못된 확장자 업로드 방지
2 parents 18edfd7 + 27917e5 commit 1e7c7b4

File tree

3 files changed

+62
-6
lines changed

3 files changed

+62
-6
lines changed

src/components/Modal/WineModal/AddWineModal.tsx

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ interface AddWineModalProps {
2727
setShowRegisterModal: (state: boolean) => void;
2828
}
2929
const AddWineModal = ({ showRegisterModal, setShowRegisterModal }: AddWineModalProps) => {
30+
const ALLOWED_IMAGE_TYPES = ['image/png', 'image/jpeg', 'image/webp'];
31+
3032
const [category, setCategory] = useState('');
3133
const fileInputRef = useRef<HTMLInputElement | null>(null);
3234
const [previewImage, setPreviewImage] = useState<string | null>(null);
@@ -38,6 +40,14 @@ const AddWineModal = ({ showRegisterModal, setShowRegisterModal }: AddWineModalP
3840
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
3941
const file = e.target.files?.[0];
4042
if (file) {
43+
if (!ALLOWED_IMAGE_TYPES.includes(file.type)) {
44+
setPreviewImage(null);
45+
setError('wineImage', {
46+
type: 'manual',
47+
message: '지원하지 않는 이미지 형식입니다. (png, jpg, webp)',
48+
});
49+
return;
50+
}
4151
const renamedFile = renameFileIfNeeded(file); //이미지파일 이름 정규화
4252
const imageUrl = URL.createObjectURL(renamedFile);
4353
setPreviewImage(imageUrl);
@@ -52,6 +62,7 @@ const AddWineModal = ({ showRegisterModal, setShowRegisterModal }: AddWineModalP
5262
clearErrors,
5363
trigger,
5464
setValue,
65+
setError,
5566
reset,
5667
} = useForm<WineForm>({
5768
mode: 'onBlur',
@@ -97,11 +108,31 @@ const AddWineModal = ({ showRegisterModal, setShowRegisterModal }: AddWineModalP
97108
},
98109
});
99110
const onSubmit = async (form: WineForm) => {
100-
let file = form.wineImage[0];
101-
file = renameFileIfNeeded(file); //이미지파일 이름 정규화
111+
const file = form.wineImage?.[0];
112+
113+
// 1. 이미지 없을 때 막기 (선택사항: 이미 react-hook-form required로 막고 있음)
114+
if (!file) {
115+
setError('wineImage', {
116+
type: 'manual',
117+
message: '이미지를 업로드해 주세요.',
118+
});
119+
return;
120+
}
121+
122+
// 2. 확장자 제한
123+
if (!ALLOWED_IMAGE_TYPES.includes(file.type)) {
124+
setError('wineImage', {
125+
type: 'manual',
126+
message: '지원하지 않는 이미지 형식입니다. (png, jpg, webp)',
127+
});
128+
return;
129+
}
130+
131+
// 3. 파일 정규화 및 제출
132+
const renamedFile = renameFileIfNeeded(file);
102133
postWineMutation.mutate({
103134
...form,
104-
wineImage: [file] as unknown as FileList,
135+
wineImage: [renamedFile] as unknown as FileList,
105136
});
106137
};
107138
const categoryOptions = [
@@ -207,7 +238,7 @@ const AddWineModal = ({ showRegisterModal, setShowRegisterModal }: AddWineModalP
207238
})}
208239
id='wineImage'
209240
type='file'
210-
accept='image/*'
241+
accept='.png, .jpg, .jpeg, .webp'
211242
className='hidden'
212243
ref={(e) => {
213244
register('wineImage').ref(e);

src/components/Modal/WineModal/EditWineModal.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ interface EditWineModalProps {
3737
}
3838

3939
const EditWineModal = ({ wine, showEditModal, setShowEditModal }: EditWineModalProps) => {
40+
const ALLOWED_IMAGE_TYPES = ['image/png', 'image/jpeg', 'image/webp'];
41+
4042
const [previewImage, setPreviewImage] = useState<string | null>(wine.image);
4143
const fileInputRef = useRef<HTMLInputElement | null>(null);
4244
const queryClient = useQueryClient();
@@ -49,6 +51,7 @@ const EditWineModal = ({ wine, showEditModal, setShowEditModal }: EditWineModalP
4951
clearErrors,
5052
reset,
5153
setValue,
54+
setError,
5255
trigger,
5356
watch,
5457
} = useForm<WineForm>({
@@ -79,6 +82,15 @@ const EditWineModal = ({ wine, showEditModal, setShowEditModal }: EditWineModalP
7982
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
8083
const file = e.target.files?.[0];
8184
if (file) {
85+
if (!ALLOWED_IMAGE_TYPES.includes(file.type)) {
86+
setPreviewImage(null);
87+
setError('wineImage', {
88+
type: 'manual',
89+
message: '지원하지 않는 이미지 형식입니다. (png, jpg, webp)',
90+
});
91+
return;
92+
}
93+
8294
const renamedFile = renameFileIfNeeded(file);
8395
setPreviewImage(URL.createObjectURL(renamedFile));
8496
}
@@ -108,6 +120,16 @@ const EditWineModal = ({ wine, showEditModal, setShowEditModal }: EditWineModalP
108120
const onSubmit = async (form: WineForm) => {
109121
try {
110122
const file = form.wineImage?.[0];
123+
124+
//파일이 있을 때 확장자 검사
125+
if (file && !ALLOWED_IMAGE_TYPES.includes(file.type)) {
126+
setError('wineImage', {
127+
type: 'manual',
128+
message: '지원하지 않는 이미지 형식입니다. (png, jpg, webp)',
129+
});
130+
return; // 제출 막기
131+
}
132+
111133
let imageUrl = wine.image;
112134

113135
if (file) {
@@ -254,7 +276,7 @@ const EditWineModal = ({ wine, showEditModal, setShowEditModal }: EditWineModalP
254276
<Input
255277
id='wineImage'
256278
type='file'
257-
accept='image/*'
279+
accept='.png, .jpg, .jpeg, .webp'
258280
className='custom-text-md-regular md:custom-text-lg-regular hidden'
259281
{...register('wineImage', {
260282
onChange: (e) => {

src/components/my-profile/ProfileImageInput.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { useRef } from 'react';
22

33
import Image from 'next/image';
4+
import { toast } from 'sonner';
45

56
import Camera from '@/assets/camera.svg';
67
import UserDefaultImg from '@/assets/icons/userDefaultImg.svg';
@@ -45,7 +46,9 @@ export function ProfileImageInput({ imageUrl, onFileSelect }: ProfileImageInputP
4546
if (file) {
4647
const allowedTypes = ['image/png', 'image/jpeg', 'image/webp'];
4748
if (!allowedTypes.includes(file.type)) {
48-
alert('지원하지 않는 이미지 형식입니다.');
49+
toast.warning('', {
50+
description: '지원하지 않는 이미지 형식입니다.',
51+
});
4952
return;
5053
}
5154

0 commit comments

Comments
 (0)