diff --git a/src/components/user/auth-form/ProfileImageContainer.tsx b/src/components/user/auth-form/ProfileImageContainer.tsx index 6d44482a..6aaa09cc 100644 --- a/src/components/user/auth-form/ProfileImageContainer.tsx +++ b/src/components/user/auth-form/ProfileImageContainer.tsx @@ -7,6 +7,8 @@ import { JPG, PNG, SVG, WEBP } from '@constants/mimeFileType'; import useToast from '@hooks/useToast'; import { useUploadProfileImage } from '@hooks/query/useUserQuery'; import useStore from '@stores/useStore'; +import { getProfileImage } from '@services/userService'; +import useAxios from '@/hooks/useAxios'; type ProfileImageContainerProps = { imageUrl: string | null; @@ -15,8 +17,24 @@ type ProfileImageContainerProps = { export default function ProfileImageContainer({ imageUrl, setImageUrl }: ProfileImageContainerProps) { const { toastWarn } = useToast(); - const { mutate: uploadImageMutate } = useUploadProfileImage(); const { editUserInfo, userInfo } = useStore(); + const { mutate: uploadImageMutate } = useUploadProfileImage(); + const { fetchData } = useAxios(getProfileImage); + const { toastError } = useToast(); + + useEffect(() => { + const handleGetProfileImage = async (uploadName: string) => { + const response = await fetchData(uploadName); + if (response == null) + return toastError('프로필 이미지 조회 도중 문제가 발생했습니다. 잠시 후 다시 시도해주세요.'); + + const blob = new Blob([response.data], { type: response.headers['content-type'] }); + const profileImageUrl = URL.createObjectURL(blob); + setImageUrl(profileImageUrl); + }; + + if (userInfo.profileImageName) handleGetProfileImage(userInfo.profileImageName); + }, [userInfo.profileImageName]); useEffect(() => { return () => { diff --git a/src/hooks/query/useUserQuery.ts b/src/hooks/query/useUserQuery.ts index 2d3b73e7..bf3a5fa0 100644 --- a/src/hooks/query/useUserQuery.ts +++ b/src/hooks/query/useUserQuery.ts @@ -47,6 +47,31 @@ export function useUploadProfileImage() { return mutation; } +export function useGetProfileImage() { + const queryClient = useQueryClient(); + const { toastSuccess, toastError } = useToast(); + const { userInfo, editUserInfo } = useStore(); + const userProfileImageQueryKey = generateProfileFileQueryKey(userInfo.userId); + + const mutation = useMutation({ + mutationFn: ({ file }: { file: File }) => + uploadProfileImage(file, { + headers: { 'Content-Type': 'multipart/form-data' }, + }), + onError: () => toastError('이미지 업로드에 실패했습니다. 다시 시도해 주세요.'), + onSuccess: (response) => { + const { imageName } = response.data; + + if (!imageName) return toastError('이미지 업로드에 실패했습니다. 다시 시도해 주세요.'); + + toastSuccess('이미지가 업로드되었습니다.'); + editUserInfo({ profileImageName: imageName }); + queryClient.invalidateQueries({ queryKey: userProfileImageQueryKey }); + }, + }); + + return mutation; +} export function useUpdateLinks() { const { userInfo } = useStore(); diff --git a/src/mocks/services/userServiceHandler.ts b/src/mocks/services/userServiceHandler.ts index f27e2cd1..66a91d43 100644 --- a/src/mocks/services/userServiceHandler.ts +++ b/src/mocks/services/userServiceHandler.ts @@ -142,6 +142,40 @@ const userServiceHandler = [ return HttpResponse.json({ imageName: uploadName }, { status: 200 }); }), + // 유저 프로필 이미지 조회 API + http.get(`${BASE_URL}/file/profile/:fileName`, async ({ request, params }) => { + const { fileName } = params; + + const accessToken = request.headers.get('Authorization'); + if (!accessToken) return new HttpResponse(null, { status: 401 }); + + const userId = convertTokenToUserId(accessToken); + if (!userId) { + return HttpResponse.json({ message: '토큰에 유저 정보가 존재하지 않습니다.' }, { status: 401 }); + } + + const userIndex = USER_DUMMY.findIndex((user) => user.userId === userId); + if (userIndex === -1) { + return HttpResponse.json( + { message: '해당 사용자를 찾을 수 없습니다. 입력 정보를 확인해 주세요.' }, + { status: 401 }, + ); + } + + const decodedFileName = decodeURIComponent(fileName.toString()); + const fileInfo = PROFILE_IMAGE_DUMMY.find((file) => file.uploadName === decodedFileName); + if (!fileInfo) return new HttpResponse(null, { status: 404 }); + + if (fileInfo.userId !== Number(userId)) + return HttpResponse.json({ message: '해당 파일에 접근 권한이 없습니다.' }, { status: 401 }); + + const buffer = await fileInfo.file.arrayBuffer(); + return HttpResponse.arrayBuffer(buffer, { + headers: { + 'Content-Type': fileInfo.file.type, + }, + }); + }), // 전체 팀 목록 조회 API (가입한 팀, 대기중인 팀) http.get(`${BASE_URL}/user/team`, ({ request }) => { const accessToken = request.headers.get('Authorization'); diff --git a/src/services/userService.ts b/src/services/userService.ts index 00bbf6bc..cf4c2d83 100644 --- a/src/services/userService.ts +++ b/src/services/userService.ts @@ -30,7 +30,7 @@ export async function updateLinks(links: EditUserLinksForm, axiosConfig: AxiosRe } /** - * 유저 프로필 업로드 API + * 유저 프로필 이미지 업로드 API * * @export * @async @@ -44,6 +44,22 @@ export async function uploadProfileImage(file: File, axiosConfig: AxiosRequestCo return authAxios.postForm(`/user/profile/image`, fileFormData, axiosConfig); } +/** + * 유저 프로필 이미지 조회 API + * + * @export + * @async + * @param {string} fileName - 파일명 + * @param {AxiosRequestConfig} [axiosConfig={}] - axios 요청 옵션 설정 객체 + * @returns {Promise>} + */ +export async function getProfileImage(fileName: string, axiosConfig: AxiosRequestConfig = {}) { + return authAxios.get(`/file/profile/${fileName}`, { + ...axiosConfig, + responseType: 'blob', + }); +} + /** * 유저 목록을 검색하는 API *