diff --git a/src/_app/pages/CreateMessagePage/Steps/PhotoInputStep/index.tsx b/src/_app/pages/CreateMessagePage/Steps/PhotoInputStep/index.tsx index ad153c4..25d23a1 100644 --- a/src/_app/pages/CreateMessagePage/Steps/PhotoInputStep/index.tsx +++ b/src/_app/pages/CreateMessagePage/Steps/PhotoInputStep/index.tsx @@ -4,55 +4,79 @@ import StepHeader from "@/components/Funnel/StepHeader"; import useToast from "@/hooks/useToast"; import { Photo } from "@/types/client"; import { isNill, isUndefined } from "@/utils"; +import { convertToWebP } from "@/utils/convertToWebP"; import clsx from "clsx"; -import { ChangeEvent, useRef } from "react"; +import { ChangeEvent, useCallback, useRef } from "react"; import { useWindowSize } from "react-use"; interface Props { photo: Photo | undefined; - setPhoto: (newPhoto: Photo) => void; + setPhoto: (newPhoto: Photo | undefined) => void; + maxSizeMB?: number; + maxWidth?: number; + maxHeight?: number; + quality?: number; } -const PhotoInputStep = ({ photo, setPhoto }: Props) => { +const PhotoInputStep = ({ + photo, + setPhoto, + maxSizeMB = 5, + maxWidth = 1920, + maxHeight = 1080, + quality = 0.8, +}: Props) => { const { setGlobalLoading } = useLoadingOverlay(); const inputRef = useRef(null); const { showToast } = useToast(); - const onClickUpload = () => { - if (isNill(inputRef.current)) { - return; - } + const handleImageConvert = useCallback( + async (file: File): Promise => { + return convertToWebP(file, { maxWidth, maxHeight, quality }); + }, + [maxWidth, maxHeight, quality] + ); + + const onClickUpload = () => { + if (isNill(inputRef.current)) return; inputRef.current.click(); }; - const onChange = async (event: ChangeEvent) => { - if (isNill(event.target) || isNill(event.target.files)) { - showToast("이미지를 불러오는 데 실패했어요.", "error"); - return; - } + const onChange = async (event: ChangeEvent) => { + try { + if (isNill(event.target) || isNill(event.target.files)) { + showToast("이미지를 불러오는 데 실패했어요.", "error"); + return; + } + const file = event.target.files[0]; + setGlobalLoading(true); - const file = event.target.files[0]; - setGlobalLoading(true); + const maxSize = maxSizeMB * 1024 * 1024; // 10MB + if (file.size > maxSize) { + setGlobalLoading(false); + showToast("이미지 용량이 너무 커요.", "error"); + return; + } - const maxSize = 5 * 1024 * 1024; // 10MB - if (file.size > maxSize) { + const optimizedPhoto = await handleImageConvert(file); + setPhoto(optimizedPhoto); + } catch (error) { + showToast("이미지 처리 중 오류가 발생했어요.", "error"); + } finally { setGlobalLoading(false); - showToast("이미지 용량이 너무 커요.", "error"); - - return; + if (inputRef.current) { + inputRef.current.value = ""; + } } + }; - const fileUrl = URL.createObjectURL(file); - - const img = new window.Image(); - img.src = fileUrl; - img.onload = () => - setPhoto({ - file, - url: fileUrl, - }); - setGlobalLoading(false); + const handleRemoveImage = () => { + if (photo?.url) { + URL.revokeObjectURL(photo.url); + } + setPhoto(undefined); }; + const { height } = useWindowSize(); return ( @@ -84,12 +108,16 @@ const PhotoInputStep = ({ photo, setPhoto }: Props) => { 업로드된 이미지 )}

- 사진은 최대 1장, 5mb까지 업로드 가능해요. + {isUndefined(photo) + ? `사진은 최대 1장, ${maxSizeMB}mb까지 업로드 가능해요.` + : "이미지를 더블클릭하면 제거할 수 있어요."}

=> /** * 이미지 파일을 WebP 형식으로 변환 - * - * @description - * 이 함수는 다음과 같은 최적화를 수행합니다: - * 1. 이미지 크기를 최대 허용 크기에 맞게 조정 - * 2. WebP 형식으로 변환하여 파일 크기 최적화 - * 3. 지정된 품질로 압축 - * + * * @param file - 변환할 이미지 파일 * @param config - 변환 설정 * @param config.maxWidth - 최대 허용 너비