diff --git a/frontend/src/api/image.ts b/frontend/src/api/image.ts index 407de9161..589c57726 100644 --- a/frontend/src/api/image.ts +++ b/frontend/src/api/image.ts @@ -3,15 +3,13 @@ import axios from 'axios'; import { ACCESSTOKEN_KEY } from '@/constants'; import { HOME_URL } from '@/constants/apiUrl'; -export const postImageUrlConverter = async (formData: FormData) => { +export const postImageUrlConverter = (formData: FormData) => { const accessToken = localStorage.getItem(ACCESSTOKEN_KEY); - const response = await axios.post(`${HOME_URL}/api/images`, formData, { + return axios.post(`${HOME_URL}/api/images`, formData, { headers: { 'Content-Type': 'multipart/form-data', Authorization: `Bearer ${accessToken}`, }, }); - - return response.data.url; }; diff --git a/frontend/src/constants/ErrorMessage.ts b/frontend/src/constants/ErrorMessage.ts index f50c40bd2..ee77c0d6d 100644 --- a/frontend/src/constants/ErrorMessage.ts +++ b/frontend/src/constants/ErrorMessage.ts @@ -35,5 +35,7 @@ export const ErrorMessage = { '5008': '투표 내역이 존재하지 않습니다.', '5009': '투표항목은 2개이상 5개 이하로 등록해주세요.', + '7002': '지원하지 않는 형식입니다.', + '9999': '런타임 에러입니다. 코드를 확인해주세요', }; diff --git a/frontend/src/hooks/comment/useHandleCommentInputModalState.tsx b/frontend/src/hooks/comment/useHandleCommentInputModalState.tsx index 423bfb452..7da15ae46 100644 --- a/frontend/src/hooks/comment/useHandleCommentInputModalState.tsx +++ b/frontend/src/hooks/comment/useHandleCommentInputModalState.tsx @@ -5,8 +5,8 @@ import usePostCommentInputModal from '@/hooks/comment/usePostCommentInputModal'; import usePutCommentInputModal from '@/hooks/comment/usePutCommentInputModal'; import useModal from '@/hooks/common/useModal'; import useSnackBar from '@/hooks/common/useSnackBar'; +import useToastImageConverter from '@/hooks/common/useToastImageConverter'; import { queryClient } from '@/index'; -import { takeToastImgEditor } from '@/utils/takeToastImgEditor'; import { validatedCommentInput } from '@/utils/validateInput'; import { Editor } from '@toast-ui/react-editor'; @@ -31,16 +31,14 @@ const useHandleCommentInputModalState = ({ isSuccess: putIsSuccess, } = usePutCommentInputModal(hideModal); + useToastImageConverter(commentContent); + useEffect(() => { if (postIsSuccess || putIsSuccess) { queryClient.refetchQueries('comments'); } }); - useEffect(() => { - takeToastImgEditor(commentContent); - }, [commentContent]); - useEffect(() => { const commentTempSavedInterval = setInterval(() => { if (commentContent.current !== null) { diff --git a/frontend/src/hooks/common/useThrowCustomError.tsx b/frontend/src/hooks/common/useThrowCustomError.tsx index d2ddb4ba1..6f089af24 100644 --- a/frontend/src/hooks/common/useThrowCustomError.tsx +++ b/frontend/src/hooks/common/useThrowCustomError.tsx @@ -1,5 +1,5 @@ import { AxiosError } from 'axios'; -import { useEffect } from 'react'; +import { Dispatch, SetStateAction, useEffect } from 'react'; import CustomError from '@/components/@helper/errorBoundary/CustomError'; import { ErrorMessage } from '@/constants/ErrorMessage'; @@ -10,8 +10,15 @@ const useThrowCustomError = ( errorCode: keyof typeof ErrorMessage; message: string; }> | null, + blockThrowError?: boolean, + setBlockThrowError?: Dispatch>, ) => { useEffect(() => { + if (blockThrowError && setBlockThrowError) { + setBlockThrowError(false); + return; + } + if (isError && error) { if (!error.response || typeof error.response.data === 'undefined') { throw new CustomError('0000', '네트워크에 문제가 발생하였습니다.'); diff --git a/frontend/src/hooks/common/useToastImageConverter.tsx b/frontend/src/hooks/common/useToastImageConverter.tsx new file mode 100644 index 000000000..cf7bae5fc --- /dev/null +++ b/frontend/src/hooks/common/useToastImageConverter.tsx @@ -0,0 +1,49 @@ +import { AxiosError, AxiosResponse } from 'axios'; +import { MutableRefObject, useEffect, useState } from 'react'; +import { useMutation } from 'react-query'; + +import { postImageUrlConverter } from '@/api/image'; +import { ErrorMessage } from '@/constants/ErrorMessage'; +import useSnackBar from '@/hooks/common/useSnackBar'; +import useThrowCustomError from '@/hooks/common/useThrowCustomError'; +import { Editor } from '@toast-ui/react-editor'; + +const useToastImageConverter = (content: MutableRefObject) => { + const { showSnackBar } = useSnackBar(); + const [blockThrowError, setBlockThrowError] = useState(false); + const { isError, error, mutateAsync } = useMutation< + AxiosResponse<{ url: string }>, + AxiosError<{ errorCode: keyof typeof ErrorMessage; message: string }>, + FormData + >(postImageUrlConverter, { + onError: (error) => { + setBlockThrowError(true); + if (typeof error.response?.data === 'undefined') { + showSnackBar('컨텐츠의 용량이 너무 큽니다.'); + return; + } + if (error.response.data.errorCode === '7002') { + showSnackBar(ErrorMessage[error.response.data.errorCode]); + return; + } + }, + }); + + useEffect(() => { + if (content.current) { + content.current.getInstance().removeHook('addImageBlobHook'); + content.current.getInstance().addHook('addImageBlobHook', (blob, callback) => { + (async () => { + const formData = new FormData(); + formData.append('file', blob); + const data = await mutateAsync(formData); + callback(data.data.url, '게시물 이미지'); + })(); + }); + } + }, [content.current]); + + useThrowCustomError(isError, error, blockThrowError, setBlockThrowError); +}; + +export default useToastImageConverter; diff --git a/frontend/src/pages/UpdateWriting/index.tsx b/frontend/src/pages/UpdateWriting/index.tsx index fd8bd0c07..7cf58e29a 100644 --- a/frontend/src/pages/UpdateWriting/index.tsx +++ b/frontend/src/pages/UpdateWriting/index.tsx @@ -6,18 +6,14 @@ import Loading from '@/components/@common/Loading/Loading'; import ToastUiEditor from '@/components/@common/ToastUiEditor/ToastUiEditor'; import HashTag from '@/components/hashTag/HashTag/HashTag'; import usePostWritingArticle from '@/hooks/article/usePostUpdateWritingArticle'; +import useToastImageConverter from '@/hooks/common/useToastImageConverter'; import * as S from '@/pages/WritingArticles/index.styles'; import { WritingCategoryCardStyle, WritingTitleCardStyle } from '@/styles/cardStyle'; -import { takeToastImgEditor } from '@/utils/takeToastImgEditor'; const UpdateWriting = () => { const { id } = useParams(); const { category } = useParams(); - if (id === undefined || category === undefined) { - throw new Error('id와 category 값을 가지고 오지 못하였습니다'); - } - const { isLoading, title, @@ -31,9 +27,11 @@ const UpdateWriting = () => { handleClickUpdateSubmitButton, } = usePostWritingArticle(); - useEffect(() => { - takeToastImgEditor(content); - }, [content]); + useToastImageConverter(content); + + if (id === undefined || category === undefined) { + throw new Error('id와 category 값을 가지고 오지 못하였습니다'); + } if (isLoading) { return ; diff --git a/frontend/src/pages/WritingArticles/index.tsx b/frontend/src/pages/WritingArticles/index.tsx index 46a1e8c4f..4a7b32e44 100644 --- a/frontend/src/pages/WritingArticles/index.tsx +++ b/frontend/src/pages/WritingArticles/index.tsx @@ -8,11 +8,11 @@ import ToastUiEditor from '@/components/@common/ToastUiEditor/ToastUiEditor'; import HashTag from '@/components/hashTag/HashTag/HashTag'; import usePostWritingArticles from '@/hooks/article/usePostWritingArticles'; import useSnackBar from '@/hooks/common/useSnackBar'; +import useToastImageConverter from '@/hooks/common/useToastImageConverter'; import useGetTempDetailArticles from '@/hooks/tempArticle/useGetTempDetailArticles'; import usePostTempArticle from '@/hooks/tempArticle/usePostTempArticle'; import * as S from '@/pages/WritingArticles/index.styles'; import { WritingCategoryCardStyle, WritingTitleCardStyle } from '@/styles/cardStyle'; -import { takeToastImgEditor } from '@/utils/takeToastImgEditor'; const WritingArticles = ({ tempId = '' }: { tempId?: '' | number }) => { const { category } = useParams(); @@ -35,6 +35,8 @@ const WritingArticles = ({ tempId = '' }: { tempId?: '' | number }) => { setHashTags, } = usePostWritingArticles({ category, isAnonymous }); + useToastImageConverter(content); + const { saveTempArticleId, isSuccess: isTempArticleSavedSuccess, @@ -84,10 +86,6 @@ const WritingArticles = ({ tempId = '' }: { tempId?: '' | number }) => { } }, [isTempDetailArticleSuccess]); - useEffect(() => { - takeToastImgEditor(content); - }, [content]); - if (isLoading) return ; const handleClickTemporaryStoreButton = () => { diff --git a/frontend/src/utils/takeToastImgEditor.ts b/frontend/src/utils/takeToastImgEditor.ts deleted file mode 100644 index d93899fc3..000000000 --- a/frontend/src/utils/takeToastImgEditor.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { MutableRefObject } from 'react'; - -import { postImageUrlConverter } from '@/api/image'; -import { Editor } from '@toast-ui/react-editor'; - -export const takeToastImgEditor = (content: MutableRefObject) => { - if (content.current) { - content.current.getInstance().removeHook('addImageBlobHook'); - content.current.getInstance().addHook('addImageBlobHook', (blob, callback) => { - (async () => { - const formData = new FormData(); - formData.append('file', blob); - const url = await postImageUrlConverter(formData); - callback(url, 'alt-text'); - })(); - }); - } -};