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} - + + + - - +