diff --git a/frontend/src/components/PostForm/index.tsx b/frontend/src/components/PostForm/index.tsx index 69ee2dad3..9e50e68f8 100644 --- a/frontend/src/components/PostForm/index.tsx +++ b/frontend/src/components/PostForm/index.tsx @@ -67,6 +67,8 @@ export default function PostForm({ data, mutate }: PostFormProps) { const contentImageHook = useContentImage( serverImageUrl && convertImageUrlToServerUrl(serverImageUrl) ); + const { handlePasteImage } = contentImageHook; + const writingOptionHook = useWritingOption( serverVoteInfo?.options.map(option => ({ ...option, @@ -233,6 +235,7 @@ export default function PostForm({ data, mutate }: PostFormProps) { placeholder={CONTENT_PLACEHOLDER} maxLength={POST_CONTENT.MAX_LENGTH} minLength={POST_CONTENT.MIN_LENGTH} + onPaste={handlePasteImage} required /> diff --git a/frontend/src/hooks/useContentImage.ts b/frontend/src/hooks/useContentImage.ts index 8a859eed2..7c0a46675 100644 --- a/frontend/src/hooks/useContentImage.ts +++ b/frontend/src/hooks/useContentImage.ts @@ -1,48 +1,43 @@ -import { ChangeEvent, useRef, useState } from 'react'; +import { ChangeEvent, ClipboardEvent, useRef, useState } from 'react'; -import { MAX_FILE_SIZE } from '@components/PostForm/constants'; - -import { convertImageToWebP } from '@utils/resizeImage'; +import { uploadImage } from '@utils/post/uploadImage'; export const useContentImage = (imageUrl: string = '') => { const [contentImage, setContentImage] = useState(imageUrl); const contentInputRef = useRef(null); + const handlePasteImage = (event: ClipboardEvent) => { + const file = event.clipboardData.files[0]; + + if (file.type.slice(0, 5) === 'image') { + event.preventDefault(); + + uploadImage({ + imageFile: file, + inputElement: contentInputRef.current, + setPreviewImageUrl: setContentImage, + }); + } + }; + const removeImage = () => { setContentImage(''); if (contentInputRef.current) contentInputRef.current.value = ''; }; - const handleUploadImage = async (event: ChangeEvent) => { + const handleUploadImage = (event: ChangeEvent) => { const { files } = event.target; if (!files) return; const file = files[0]; - const webpFileList = await convertImageToWebP(file); - - event.target.files = webpFileList; - - const reader = new FileReader(); - - const webpFile = webpFileList[0]; - - reader.readAsDataURL(webpFile); - - event.target.setCustomValidity(''); - - if (file.size > MAX_FILE_SIZE) { - event.target.setCustomValidity('사진의 용량은 1.5MB 이하만 가능합니다.'); - event.target.reportValidity(); - - return; - } - - reader.onloadend = () => { - setContentImage(reader.result?.toString() ?? ''); - }; + uploadImage({ + imageFile: file, + inputElement: contentInputRef.current, + setPreviewImageUrl: setContentImage, + }); }; - return { contentImage, contentInputRef, removeImage, handleUploadImage }; + return { contentImage, contentInputRef, removeImage, handleUploadImage, handlePasteImage }; }; diff --git a/frontend/src/hooks/useWritingOption.tsx b/frontend/src/hooks/useWritingOption.tsx index 4b465b0c6..4083c08ff 100644 --- a/frontend/src/hooks/useWritingOption.tsx +++ b/frontend/src/hooks/useWritingOption.tsx @@ -1,8 +1,6 @@ import React, { ChangeEvent, useState } from 'react'; -import { MAX_FILE_SIZE } from '@components/PostForm/constants'; - -import { convertImageToWebP } from '@utils/resizeImage'; +import { uploadImage } from '@utils/post/uploadImage'; const MAX_WRITING_LENGTH = 50; @@ -80,6 +78,18 @@ export const useWritingOption = (initialOptionList: WritingVoteOptionType[] = IN setOptionList(updatedOptionList); }; + const setPreviewImageUrl = (optionId: number) => (imageUrl: string) => { + const updatedOptionList = optionList.map(optionItem => { + if (optionItem.id === optionId) { + return { ...optionItem, imageUrl }; + } + + return optionItem; + }); + + setOptionList(updatedOptionList); + }; + const handleUploadImage = async ( event: React.ChangeEvent, optionId: number @@ -90,36 +100,11 @@ export const useWritingOption = (initialOptionList: WritingVoteOptionType[] = IN const file = files[0]; - const webpFileList = await convertImageToWebP(file); - - event.target.files = webpFileList; - - const reader = new FileReader(); - - const webpFile = webpFileList[0]; - - reader.readAsDataURL(webpFile); - - event.target.setCustomValidity(''); - - if (file.size > MAX_FILE_SIZE) { - event.target.setCustomValidity('사진의 용량은 1.5MB 이하만 가능합니다.'); - event.target.reportValidity(); - - return; - } - - reader.onloadend = () => { - const updatedOptionList = optionList.map(optionItem => { - if (optionItem.id === optionId) { - return { ...optionItem, imageUrl: reader.result?.toString() ?? '' }; - } - - return optionItem; - }); - - setOptionList(updatedOptionList); - }; + uploadImage({ + imageFile: file, + inputElement: event.target, + setPreviewImageUrl: setPreviewImageUrl(optionId), + }); }; return { optionList, addOption, writingOption, deleteOption, removeImage, handleUploadImage }; diff --git a/frontend/src/utils/post/uploadImage.ts b/frontend/src/utils/post/uploadImage.ts new file mode 100644 index 000000000..98fdfb650 --- /dev/null +++ b/frontend/src/utils/post/uploadImage.ts @@ -0,0 +1,38 @@ +import { MAX_FILE_SIZE } from '@constants/post'; + +import { convertImageToWebP } from '@utils/resizeImage'; + +export const uploadImage = async ({ + imageFile, + inputElement, + setPreviewImageUrl, +}: { + imageFile: File; + inputElement: HTMLInputElement | null; + setPreviewImageUrl: (previewUrl: string) => void; +}) => { + if (!inputElement) return; + + const webpFileList = await convertImageToWebP(imageFile); + + inputElement.files = webpFileList; + + const reader = new FileReader(); + + const webpFile = webpFileList[0]; + + reader.readAsDataURL(webpFile); + + inputElement.setCustomValidity(''); + + if (imageFile.size > MAX_FILE_SIZE) { + inputElement.setCustomValidity('사진의 용량은 1.5MB 이하만 가능합니다.'); + inputElement.reportValidity(); + + return; + } + + reader.onloadend = () => { + setPreviewImageUrl(reader.result?.toString() ?? ''); + }; +};