From df3b1b8802e186eebf21d0bf671a3d35d51f2822 Mon Sep 17 00:00:00 2001 From: woo hyeonji <117665863+Eosdia@users.noreply.github.com> Date: Thu, 30 Nov 2023 17:50:37 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=B4=89=EC=82=AC=EB=AA=A8=EC=A7=91=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=B3=B4?= =?UTF-8?q?=ED=98=B8=EC=86=8C=ED=94=84=EB=A1=9C=ED=95=84=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99,=20=EB=B4=89=EC=82=AC=20=EC=8B=A0=EC=B2=AD=20msw=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#245)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(volunteer): 봉사모집상세조회, 보호소 간단정보조회 fetch hook 분리 * refactor(volunteer): 봉사모집상세조회 데이터 받아오기 * feat(volunteer): 봉사신청 mock api 추가 * fix(volunteer): 봉사신청 api 수정 * feat(volunteer): 봉사신청 msw 연결 * fix(volunteer): 채팅하기 버튼 제거 * feat(volunteer): 보호소 정보 클릭하면 보호소 프로필 페이지로 이동 * feat(volunteer): 봉사신청 자동마감 된 경우 추가 --- apps/volunteer/src/apis/recruitment.ts | 4 +- .../src/mocks/handlers/recruitment.ts | 5 + .../_hooks/useFetchRecruitmentDetail.ts | 9 + .../_hooks/useFetchSimpleShelterInfo.ts | 11 ++ .../detail/_hooks/useFetchVolunteerDetail.ts | 74 ------- .../src/pages/volunteers/detail/index.tsx | 181 +++++++++++------- 6 files changed, 138 insertions(+), 146 deletions(-) create mode 100644 apps/volunteer/src/pages/volunteers/detail/_hooks/useFetchRecruitmentDetail.ts create mode 100644 apps/volunteer/src/pages/volunteers/detail/_hooks/useFetchSimpleShelterInfo.ts delete mode 100644 apps/volunteer/src/pages/volunteers/detail/_hooks/useFetchVolunteerDetail.ts diff --git a/apps/volunteer/src/apis/recruitment.ts b/apps/volunteer/src/apis/recruitment.ts index cb3541b6..b367051b 100644 --- a/apps/volunteer/src/apis/recruitment.ts +++ b/apps/volunteer/src/apis/recruitment.ts @@ -5,8 +5,8 @@ import { RecruitmentsRequest, } from '@/types/apis/recruitment'; -export const ApplyRecruitments = (recruitmentId: string) => - axiosInstance.post(`/recruitments/${recruitmentId}/apply`); +export const applyRecruitments = (recruitmentId: number) => + axiosInstance.post(`/volunteers/recruitments/${recruitmentId}/apply`); export const getRecruitments = (request: Partial) => axiosInstance.get( diff --git a/apps/volunteer/src/mocks/handlers/recruitment.ts b/apps/volunteer/src/mocks/handlers/recruitment.ts index dbdb35be..d6df7485 100644 --- a/apps/volunteer/src/mocks/handlers/recruitment.ts +++ b/apps/volunteer/src/mocks/handlers/recruitment.ts @@ -54,4 +54,9 @@ export const handlers = [ { status: 200 }, ); }), + http.post('/volunteers/recruitments/:recruitmentId/apply', async () => { + return new HttpResponse(null, { + status: 204, + }); + }), ]; diff --git a/apps/volunteer/src/pages/volunteers/detail/_hooks/useFetchRecruitmentDetail.ts b/apps/volunteer/src/pages/volunteers/detail/_hooks/useFetchRecruitmentDetail.ts new file mode 100644 index 00000000..c705e9d2 --- /dev/null +++ b/apps/volunteer/src/pages/volunteers/detail/_hooks/useFetchRecruitmentDetail.ts @@ -0,0 +1,9 @@ +import { useSuspenseQuery } from '@tanstack/react-query'; +import { getRecruitmentDetail } from 'shared/apis/common/Recruitments'; + +const useFetchRecruitmentDetail = (recruitmentId: number) => + useSuspenseQuery({ + queryKey: ['recruitment', recruitmentId], + queryFn: async () => (await getRecruitmentDetail(recruitmentId)).data, + }); +export default useFetchRecruitmentDetail; diff --git a/apps/volunteer/src/pages/volunteers/detail/_hooks/useFetchSimpleShelterInfo.ts b/apps/volunteer/src/pages/volunteers/detail/_hooks/useFetchSimpleShelterInfo.ts new file mode 100644 index 00000000..6d7cfd64 --- /dev/null +++ b/apps/volunteer/src/pages/volunteers/detail/_hooks/useFetchSimpleShelterInfo.ts @@ -0,0 +1,11 @@ +import { useSuspenseQuery } from '@tanstack/react-query'; + +import { getSimpleShelterProfile } from '@/apis/shelter'; + +const useFetchSimpleShelterInfo = (shelterId: number) => + useSuspenseQuery({ + queryKey: ['shelter', 'simpleProfile', shelterId], + queryFn: async () => (await getSimpleShelterProfile(shelterId)).data, + }); + +export default useFetchSimpleShelterInfo; diff --git a/apps/volunteer/src/pages/volunteers/detail/_hooks/useFetchVolunteerDetail.ts b/apps/volunteer/src/pages/volunteers/detail/_hooks/useFetchVolunteerDetail.ts deleted file mode 100644 index 55a8b42d..00000000 --- a/apps/volunteer/src/pages/volunteers/detail/_hooks/useFetchVolunteerDetail.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import { getRecruitmentDetail } from 'shared/apis/common/Recruitments'; -import { - createFormattedTime, - createWeekDayLocalString, -} from 'shared/utils/date'; - -import { getSimpleShelterProfile } from '@/apis/shelter'; - -const useFetchVolunteerDetail = (recruitmentId: number) => - useQuery({ - queryKey: ['volunteer', recruitmentId], - queryFn: async () => { - const { shelterId, ...recruitmentInfo } = ( - await getRecruitmentDetail(recruitmentId) - ).data; - const shelterSimpleInfo = (await getSimpleShelterProfile(shelterId)).data; - - return { - ...recruitmentInfo, - shelterInfo: { shelterId, ...shelterSimpleInfo }, - }; - }, - select: (data) => { - const startDate = new Date(data.recruitmentStartTime); - const endDate = new Date(data.recruitmentEndTime); - const deadLine = new Date(data.recruitmentDeadline); - - return { - imageUrls: data.recruitmentImageUrls, - title: data.recruitmentTitle, - content: data.recruitmentContent, - applicant: data.recruitmentApplicantCount, - capacity: data.recruitmentCapacity, - volunteerDay: `${createFormattedTime( - startDate, - )}(${createWeekDayLocalString(startDate)})`, - recruitmentDeadline: `${createFormattedTime( - deadLine, - )}(${createWeekDayLocalString(deadLine)}) ${createFormattedTime( - deadLine, - 'hh:mm', - )}`, - volunteerStartTime: createFormattedTime(startDate, 'hh:mm'), - volunteerEndTime: createFormattedTime(endDate, 'hh:mm'), - recruitmentCreatedAt: createFormattedTime( - new Date(data.recruitmentCreatedAt), - ), - recruitmentIsClosed: data.recruitmentIsClosed, - shelterInfo: data.shelterInfo, - }; - }, - initialData: { - recruitmentTitle: '', - recruitmentApplicantCount: 0, - recruitmentCapacity: 0, - recruitmentContent: '', - recruitmentStartTime: '', - recruitmentEndTime: '', - recruitmentIsClosed: false, - recruitmentDeadline: '', - recruitmentCreatedAt: '', - recruitmentUpdatedAt: '', - recruitmentImageUrls: [], - shelterInfo: { - shelterId: 0, - shelterName: '', - shelterImageUrl: '', - shelterAddress: '', - shelterEmail: '', - }, - }, - }); -export default useFetchVolunteerDetail; diff --git a/apps/volunteer/src/pages/volunteers/detail/index.tsx b/apps/volunteer/src/pages/volunteers/detail/index.tsx index ace21194..3e96d174 100644 --- a/apps/volunteer/src/pages/volunteers/detail/index.tsx +++ b/apps/volunteer/src/pages/volunteers/detail/index.tsx @@ -5,120 +5,160 @@ import { HStack, Text, useDisclosure, + useToast, VStack, } from '@chakra-ui/react'; -import { useEffect, useState } from 'react'; +import { useMutation } from '@tanstack/react-query'; +import { useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import AlertModal from 'shared/components/AlertModal'; import ImageCarousel from 'shared/components/ImageCarousel'; import InfoTextList from 'shared/components/InfoTextList'; -import { LabelProps } from 'shared/components/Label'; +import Label from 'shared/components/Label'; import LabelText from 'shared/components/LabelText'; import ProfileInfo from 'shared/components/ProfileInfo'; -import { getDDay } from 'shared/utils/date'; +import { + createFormattedTime, + createWeekDayLocalString, + getDDay, +} from 'shared/utils/date'; + +import { applyRecruitments } from '@/apis/recruitment'; -import useFetchVolunteerDetail from './_hooks/useFetchVolunteerDetail'; +import useFetchVolunteerDetail from './_hooks/useFetchRecruitmentDetail'; +import useFetchSimpleShelterInfo from './_hooks/useFetchSimpleShelterInfo'; export default function VolunteersDetailPage() { + const toast = useToast(); const navigate = useNavigate(); - const { id } = useParams(); + + const { id } = useParams<{ id: string }>(); const recruitmentId = Number(id); + const { isOpen, onOpen, onClose } = useDisclosure(); - const [label, setLabel] = useState({ - labelTitle: '모집중', - type: 'GREEN', + + const { data } = useFetchVolunteerDetail(recruitmentId); + + const [isRecruitmentClosed, setIsRecruitmentClosed] = useState( + data.recruitmentIsClosed, + ); + + const volunteerDay = new Date(data.recruitmentStartTime); + const deadline = new Date(data.recruitmentDeadline); + const createdAt = new Date(data.recruitmentCreatedAt); + const updatedAt = new Date(data.recruitmentUpdatedAt); + + const { data: shelter } = useFetchSimpleShelterInfo(data.shelterId); + + const { mutate: applyRecruitment } = useMutation({ + mutationFn: async () => await applyRecruitments(recruitmentId), + onSuccess: () => { + toast({ + position: 'top', + description: '봉사 신청이 완료되었습니다.', + status: 'success', + duration: 1500, + }); + }, + onError: (error) => { + if (error.response?.status === 409) { + toast({ + position: 'top', + description: '봉사 모집이 마감되었습니다.', + status: 'error', + duration: 1500, + }); + setIsRecruitmentClosed(!isRecruitmentClosed); + } + }, }); - const { data } = useFetchVolunteerDetail(3); - - const { - imageUrls, - title, - content, - applicant, - capacity, - volunteerDay, - recruitmentDeadline, - volunteerStartTime, - volunteerEndTime, - recruitmentCreatedAt, - recruitmentIsClosed, - shelterInfo, - } = data; - const { shelterName, shelterImageUrl, shelterAddress, shelterEmail } = - shelterInfo; - - useEffect(() => { - if (recruitmentIsClosed) { - setLabel({ labelTitle: '마감완료', type: 'GRAY' }); - } - }, [recruitmentIsClosed]); - - const goChatting = () => { - //TODO 채팅방 생성 API - navigate(`/chattings/${recruitmentId}`); + const goShelterProfilePage = () => { + navigate(`/shelters/profile/${data.shelterId}`); }; const onApplyRecruitment = () => { onClose(); - //TODO 봉사신청 API - //TODO 봉사신청완료 toast + applyRecruitment(); }; return ( - + - + {isRecruitmentClosed ? ( + - {content} + {data.recruitmentContent} - + + + - - +