From ef0a5613d78e2bb0203f2ce2474c45b1c95598a7 Mon Sep 17 00:00:00 2001 From: pookey1104 Date: Sat, 15 Feb 2025 17:28:41 +0900 Subject: [PATCH] =?UTF-8?q?Feat:=20=EB=82=B4=20=EA=B3=B5=EA=B0=84=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/artwork-detail/postMySpace.ts | 37 ++++++ src/apis/artwork-detail/type.ts | 6 + src/constants/mutationKey.ts | 12 ++ .../components/MySpaceModal/index.style.ts | 3 +- .../components/MySpaceModal/index.tsx | 113 +++++++++++------- .../artwork-detail/hooks/useMySpaceForm.ts | 76 ++++++++++++ src/pages/artwork-detail/index.tsx | 10 +- 7 files changed, 207 insertions(+), 50 deletions(-) create mode 100644 src/apis/artwork-detail/postMySpace.ts create mode 100644 src/pages/artwork-detail/hooks/useMySpaceForm.ts diff --git a/src/apis/artwork-detail/postMySpace.ts b/src/apis/artwork-detail/postMySpace.ts new file mode 100644 index 0000000..0842d90 --- /dev/null +++ b/src/apis/artwork-detail/postMySpace.ts @@ -0,0 +1,37 @@ +import { instance } from '@/apis/axios'; +import { TGetResponse } from '@/apis/type'; +import { TMySpaceFormData } from './type'; + +/** + * 내 공간 등록 API 함수 + * @param data 공간 등록 폼 데이터 (파일 전송을 위해 FormData 형태로 변환됩니다.) + * @returns 등록 결과를 포함한 응답(TGetResponse) + * @author 김서윤 + */ + +export const postMySpace = async ( + data: TMySpaceFormData +): Promise> => { + // 이미지 파일과 텍스트 필드를 함께 전송하기 위해 FormData 사용 + const formData = new FormData(); + + if (data.images) { + formData.append('images', data.images); + } + + // 나머지 텍스트 데이터 추가 + formData.append('name', data.name); + formData.append('area', data.area); + + const response = await instance.post>( + '/api/userspace', + formData, + { + headers: { + 'Content-Type': 'multipart/form-data', + }, + } + ); + console.log(response.data); + return response.data; +}; diff --git a/src/apis/artwork-detail/type.ts b/src/apis/artwork-detail/type.ts index b23442d..da1000b 100644 --- a/src/apis/artwork-detail/type.ts +++ b/src/apis/artwork-detail/type.ts @@ -44,3 +44,9 @@ export type TOtherArtwork = { title: string; thumbnail: string; }; + +export type TMySpaceFormData = { + images?: File; + name: string; + area: string; +}; diff --git a/src/constants/mutationKey.ts b/src/constants/mutationKey.ts index 4217006..48ea67a 100644 --- a/src/constants/mutationKey.ts +++ b/src/constants/mutationKey.ts @@ -4,6 +4,7 @@ import { postArtworkRegister } from '@/apis/artworkRegister/postArtworkRegister' import { getAvailableArtworksQuery } from '@/constants/queryKeys'; import { postAuctionBid } from '@/apis/auction/postAuctionBid'; import { toggleArtworkLike } from '@/apis/artwork-like/like'; +import { postMySpace } from '@/apis/artwork-detail/postMySpace'; /** * 인증 회원가입 뮤테이션 * @author 홍규진 @@ -59,3 +60,14 @@ export const toggleArtworkLikeMutation = (artworkId: number) => { mutationFn: () => toggleArtworkLike(artworkId), }; }; + +/** + * 내 공간 등록 뮤테이션 + * @author 김서윤 + * */ +export const postMySpaceMutation = () => { + return { + mutationKey: ['new_userspace'], + mutationFn: postMySpace, + }; +}; diff --git a/src/pages/artwork-detail/components/MySpaceModal/index.style.ts b/src/pages/artwork-detail/components/MySpaceModal/index.style.ts index 7d3f562..1f87a9f 100644 --- a/src/pages/artwork-detail/components/MySpaceModal/index.style.ts +++ b/src/pages/artwork-detail/components/MySpaceModal/index.style.ts @@ -109,7 +109,7 @@ export const BottomContainer = styled.div` padding: 16px 32px 24px; `; -export const CloseBtn = styled.div` +export const CloseBtn = styled.button` padding: 9px 38.5px; background-color: ${({ $isCancle }) => $isCancle ? `${theme.colors.white}` : `${theme.colors.black}`}; @@ -118,6 +118,7 @@ export const CloseBtn = styled.div` ? `1px solid ${theme.colors.lightGray}` : `1px solid ${theme.colors.black}`}; cursor: pointer; + outline: none; display: flex; justify-content: center; align-items: center; diff --git a/src/pages/artwork-detail/components/MySpaceModal/index.tsx b/src/pages/artwork-detail/components/MySpaceModal/index.tsx index 8ce37a3..d32ca5d 100644 --- a/src/pages/artwork-detail/components/MySpaceModal/index.tsx +++ b/src/pages/artwork-detail/components/MySpaceModal/index.tsx @@ -21,6 +21,8 @@ import { FilePreview, } from './index.style.ts'; import { Text } from '@/styles/text'; +import useMySpaceForm from '@/pages/artwork-detail/hooks/useMySpaceForm.ts'; +import { TMySpaceFormData } from '@/apis/artwork-detail/type'; import Close from '@/assets/svg/icon-close.svg'; import Upload from '@/assets/svg/space-register.svg'; @@ -30,6 +32,7 @@ interface MySpaceProps { export const MySpaceModal = ({ onClose }: MySpaceProps) => { const [imagePreview, setImagePreview] = useState(null); + const { formData, setFormData, handleSubmit } = useMySpaceForm(); const handleProfileImageChange = ( event: React.ChangeEvent @@ -41,6 +44,7 @@ export const MySpaceModal = ({ onClose }: MySpaceProps) => { // 🔹 미리보기 이미지 URL 생성 const previewUrl = URL.createObjectURL(imageFile); setImagePreview(previewUrl); + setFormData((prev: TMySpaceFormData) => ({ ...prev, images: imageFile })); }; return ( @@ -56,53 +60,72 @@ export const MySpaceModal = ({ onClose }: MySpaceProps) => { - - - - 공간명 - + + + + 공간명 + + setFormData((prev: TMySpaceFormData) => ({ + ...prev, + name: e.target.value, + })) + } + /> + + + 공간넓이 + + setFormData((prev: TMySpaceFormData) => ({ + ...prev, + area: e.target.value, + })) + } + /> + + m + + + + + {/* 이미지 업로드 */} + + + + + - - - 공간넓이 - - - m - - - + {imagePreview && ( + + )} + + - {/* 이미지 업로드 */} - - - - - - {imagePreview && ( - - )} - - - - - - 취소 - - - - - 확인 - - - + + + + 취소 + + + + + 확인 + + + + diff --git a/src/pages/artwork-detail/hooks/useMySpaceForm.ts b/src/pages/artwork-detail/hooks/useMySpaceForm.ts new file mode 100644 index 0000000..8009811 --- /dev/null +++ b/src/pages/artwork-detail/hooks/useMySpaceForm.ts @@ -0,0 +1,76 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { postMySpaceMutation } from '@/constants/mutationKey'; +import { TMySpaceFormData } from '@/apis/artwork-detail/type'; +import { toast } from 'sonner'; +import { useState } from 'react'; +import { getAvailableArtworksQuery } from '@/constants/queryKeys'; +const useMySpaceForm = () => { + const queryClient = useQueryClient(); + const [formData, setFormData] = useState({ + images: undefined, + name: '', + area: '', + }); + + /** + * 유효성 검사 함수 + * @param formData 폼 데이터 + * @returns 유효성 검사 결과 + * @author 김서윤 + */ + const validateForm = (formData: TMySpaceFormData) => { + const { images, name, area } = formData; + + if (!images || !name || !area) { + toast.error('모든 항목을 입력해주세요. 빈칸이 있습니다.'); + return false; + } + return true; + }; + + /** + * 내 공간 등록 뮤테이션 + * @author 김서윤 + */ + const mutation = useMutation({ + mutationKey: postMySpaceMutation().mutationKey, + mutationFn: (data: TMySpaceFormData) => + postMySpaceMutation().mutationFn(data), + onSuccess: async () => { + toast.success('공간 등록이 성공적으로 완료되었습니다.'); + queryClient.invalidateQueries({ + queryKey: postMySpaceMutation().mutationKey, + }); + await getAvailableArtworksQuery().queryFn(); + }, + onError: (error: Error) => { + toast.error(`공간 등록 실패: ${error.message}`); + }, + }); + + /** + * 공간 등록 폼 제출 함수 + * 비어있는 것도 알려주기 + * @param e 폼 제출 이벤트 + * @author 김서윤 + */ + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (validateForm(formData)) { + await mutation.mutateAsync(formData); + } else { + //어차피 위에서 토스트 메시지 띄워줬으니 여기서는 리턴 + return; + } + }; + + return { + formData, + setFormData, + handleSubmit, + isLoading: mutation.isPending, + isError: mutation.isError, + }; +}; + +export default useMySpaceForm; diff --git a/src/pages/artwork-detail/index.tsx b/src/pages/artwork-detail/index.tsx index 8a52d26..20dacf1 100644 --- a/src/pages/artwork-detail/index.tsx +++ b/src/pages/artwork-detail/index.tsx @@ -148,9 +148,9 @@ export const ArtworkDetail = () => { }; const handleUserNextClick = () => { - if (startIndex + itemsPerPage >= userspaces.length) return; + if (startIndex + itemsPerPage >= combinedSpaces.length) return; setStartIndex((prevIndex) => - Math.min(prevIndex + itemsPerPage, userspaces.length - itemsPerPage) + Math.min(prevIndex + itemsPerPage, combinedSpaces.length - itemsPerPage) ); }; @@ -272,9 +272,11 @@ export const ArtworkDetail = () => { onClick={handleUserNextClick} style={{ opacity: - startIndex + itemsPerPage >= userspaces.length ? 0.5 : 1, + startIndex + itemsPerPage >= combinedSpaces.length + ? 0.5 + : 1, cursor: - startIndex + itemsPerPage >= userspaces.length + startIndex + itemsPerPage >= combinedSpaces.length ? 'default' : 'pointer', }}