diff --git a/components/Modal/ImageUploadModal.tsx b/components/Modal/ImageUploadModal.tsx index 25b4468..d7538c6 100644 --- a/components/Modal/ImageUploadModal.tsx +++ b/components/Modal/ImageUploadModal.tsx @@ -3,6 +3,8 @@ import React, { useEffect, useRef, useState } from 'react'; import Button from '../Button'; import Modal from './Modal'; +import SnackBar from '../SnackBar'; +import useSnackBar from '@/hooks/useSanckBar'; interface ImageUploadModalProps { imageFile?: File | null; @@ -11,12 +13,10 @@ interface ImageUploadModalProps { onGetImageFile: (file: File | null) => void; } -/** - * 이미지 업로드 모달 컴포넌트 - * @param isOpen 모달 오픈 여부 - * @param onClose 모달 닫기 함수 - * @returns ImageUploadModal 컴포넌트 - */ +const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB 를 바이트로 +const NOT_SVG_MESSAGE = + 'JPG, PNG, GIF, WebP 형식의 이미지만 업로드 가능합니다.'; +const SIZE_LIMIT_MESSAGE = '파일 크기는 5MB를 초과할 수 없습니다.'; const ImageUploadModal = ({ imageFile: initialImageFile = null, @@ -24,76 +24,110 @@ const ImageUploadModal = ({ onClose, onGetImageFile, }: ImageUploadModalProps) => { - // input 요소를 참조하기 위한 ref const fileInputRef = useRef(null); - // 선택한 이미지의 미리보기 url const [previewUrl, setPreviewUrl] = useState(null); const [imageFile, setImageFile] = useState(initialImageFile); - const [isDragging, setIsDragging] = useState(false); // 드래그 중인지 여부 + const [isDragging, setIsDragging] = useState(false); + const [isValidFile, setIsValidFile] = useState(true); + const { snackBarValues, snackBarOpen } = useSnackBar(); + + const validateFile = (file: File): boolean => { + setIsValidFile(true); + + const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']; + if (!allowedTypes.includes(file.type)) { + snackBarOpen('fail', NOT_SVG_MESSAGE); + setIsValidFile(false); + return false; + } + + if (file.size > MAX_FILE_SIZE) { + snackBarOpen('fail', SIZE_LIMIT_MESSAGE); + setIsValidFile(false); + return false; + } + + return true; + }; - // 파일 선택시 const handleFileChange = (e: React.ChangeEvent) => { - // console.log('--- handleFileChange', e.target.files); const file = e.target.files?.[0]; if (file) { - const previewUrl = URL.createObjectURL(file); - setPreviewUrl(previewUrl); - setImageFile(file); + if (validateFile(file)) { + const previewUrl = URL.createObjectURL(file); + setPreviewUrl(previewUrl); + setImageFile(file); + } else if (fileInputRef.current) { + fileInputRef.current.value = ''; + } } }; - // 이미지 파일 삭제 const handleImageDelete = () => { - // console.log('--- handleImageDelete'); if (fileInputRef.current) { fileInputRef.current.value = ''; setPreviewUrl(null); setImageFile(null); + setIsValidFile(true); } }; - // 드래그 관련 이벤트 - // 드래그 시작 const handleDragEnter = (e: React.DragEvent) => { e.preventDefault(); - e.stopPropagation(); // 이벤트 전파 중지 + e.stopPropagation(); setIsDragging(true); + + if (e.dataTransfer.items && e.dataTransfer.items.length > 0) { + const file = e.dataTransfer.items[0]?.getAsFile(); + if (file) { + const allowedTypes = [ + 'image/jpeg', + 'image/png', + 'image/gif', + 'image/webp', + ]; + setIsValidFile(allowedTypes.includes(file.type)); + } + } }; - // 드래그가 영역을 벗어날 때 + const handleDragLeave = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragging(false); + setIsValidFile(true); // 드래그가 끝나면 상태 초기화 }; - // 드래그 중 + const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); }; - // 드래그로 파일을 드랍했을 때 + const handleDrop = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragging(false); - // length로 파일 존재 여부를 먼저 체크 if (e.dataTransfer.files.length > 0) { const file = e.dataTransfer.files[0]; - if (file) { + if (file && validateFile(file)) { setPreviewUrl(URL.createObjectURL(file)); setImageFile(file); } } }; - - // 썸네일로 선택 const handleImageSelect = () => { onGetImageFile(imageFile); onClose(); }; useEffect(() => { - // cleanup + if (isOpen) { + setIsValidFile(true); + } + }, [isOpen]); + + useEffect(() => { return () => { if (previewUrl !== null) { URL.revokeObjectURL(previewUrl); @@ -110,14 +144,14 @@ const ImageUploadModal = ({ return ( -
+

이미지

{previewUrl === null ? ( @@ -129,7 +163,9 @@ const ImageUploadModal = ({ onDrop={handleDrop} className={`flex h-40 w-full cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed ${ isDragging - ? 'border-green-400 bg-green-50' + ? isValidFile + ? 'border-green-300 bg-green-100' + : 'border-red-100 bg-red-50' : 'border-gray-300 bg-gray-100 hover:border-gray-400' }`} > @@ -142,6 +178,7 @@ const ImageUploadModal = ({

클릭 또는 이미지를 드래그하여 올려주세요.

+

(최대 5MB)

) : (
@@ -173,6 +210,14 @@ const ImageUploadModal = ({
+ + + {snackBarValues.children} + ); }; diff --git a/components/Modal/Modal.tsx b/components/Modal/Modal.tsx index 4ef6b1b..e041d50 100644 --- a/components/Modal/Modal.tsx +++ b/components/Modal/Modal.tsx @@ -68,7 +68,7 @@ const Modal = ({ />
{/* 닫기 버튼 영역 */} {/* 컨텐츠 영역 */} -
{children}
+
{children}
); diff --git a/components/Modal/WikiQuizModal.tsx b/components/Modal/WikiQuizModal.tsx index d43ffba..d9c2ebb 100644 --- a/components/Modal/WikiQuizModal.tsx +++ b/components/Modal/WikiQuizModal.tsx @@ -70,7 +70,7 @@ function WikiQuizModal({ }`} >