diff --git a/apps/volunteer/src/apis/recruitment.ts b/apps/volunteer/src/apis/recruitment.ts index b367051b..321f0374 100644 --- a/apps/volunteer/src/apis/recruitment.ts +++ b/apps/volunteer/src/apis/recruitment.ts @@ -13,3 +13,12 @@ export const getRecruitments = (request: Partial) => '/recruitments', { params: request }, ); + +type IsAppliedRecruitmentResponse = { + isAppliedRecruitment: boolean; +}; + +export const getIsAppliedRecruitment = (recruitmentId: number) => + axiosInstance.get( + `/volunteers/recruitments/${recruitmentId}/apply`, + ); diff --git a/apps/volunteer/src/mocks/handlers/recruitment.ts b/apps/volunteer/src/mocks/handlers/recruitment.ts index d6df7485..8366356c 100644 --- a/apps/volunteer/src/mocks/handlers/recruitment.ts +++ b/apps/volunteer/src/mocks/handlers/recruitment.ts @@ -59,4 +59,9 @@ export const handlers = [ status: 204, }); }), + http.get('/volunteers/recruitments/:recruitmentId/apply', async () => { + return HttpResponse.json({ + isAppliedRecruitment: false, + }); + }), ]; diff --git a/apps/volunteer/src/pages/volunteers/detail/_hooks/useFetchRecruitmentDetail.ts b/apps/volunteer/src/pages/volunteers/detail/_hooks/useFetchRecruitmentDetail.ts index c705e9d2..167840b6 100644 --- a/apps/volunteer/src/pages/volunteers/detail/_hooks/useFetchRecruitmentDetail.ts +++ b/apps/volunteer/src/pages/volunteers/detail/_hooks/useFetchRecruitmentDetail.ts @@ -1,9 +1,20 @@ -import { useSuspenseQuery } from '@tanstack/react-query'; +import { useSuspenseQueries } from '@tanstack/react-query'; import { getRecruitmentDetail } from 'shared/apis/common/Recruitments'; +import { getIsAppliedRecruitment } from '@/apis/recruitment'; + const useFetchRecruitmentDetail = (recruitmentId: number) => - useSuspenseQuery({ - queryKey: ['recruitment', recruitmentId], - queryFn: async () => (await getRecruitmentDetail(recruitmentId)).data, + useSuspenseQueries({ + queries: [ + { + queryKey: ['recruitment', recruitmentId], + queryFn: async () => (await getRecruitmentDetail(recruitmentId)).data, + }, + { + queryKey: ['recruitment', recruitmentId, 'isApplied'], + queryFn: async () => + (await getIsAppliedRecruitment(recruitmentId)).data, + }, + ], }); export default useFetchRecruitmentDetail; diff --git a/apps/volunteer/src/pages/volunteers/detail/index.tsx b/apps/volunteer/src/pages/volunteers/detail/index.tsx index 3e96d174..0cf0ec1b 100644 --- a/apps/volunteer/src/pages/volunteers/detail/index.tsx +++ b/apps/volunteer/src/pages/volunteers/detail/index.tsx @@ -8,8 +8,8 @@ import { useToast, VStack, } from '@chakra-ui/react'; -import { useMutation } from '@tanstack/react-query'; -import { useState } from 'react'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { Suspense, useEffect, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import AlertModal from 'shared/components/AlertModal'; import ImageCarousel from 'shared/components/ImageCarousel'; @@ -17,6 +17,7 @@ import InfoTextList from 'shared/components/InfoTextList'; import Label from 'shared/components/Label'; import LabelText from 'shared/components/LabelText'; import ProfileInfo from 'shared/components/ProfileInfo'; +import useAuthStore from 'shared/store/authStore'; import { createFormattedTime, createWeekDayLocalString, @@ -28,31 +29,70 @@ import { applyRecruitments } from '@/apis/recruitment'; import useFetchVolunteerDetail from './_hooks/useFetchRecruitmentDetail'; import useFetchSimpleShelterInfo from './_hooks/useFetchSimpleShelterInfo'; -export default function VolunteersDetailPage() { - const toast = useToast(); +function VolunteersDetail() { const navigate = useNavigate(); + const { user } = useAuthStore(); const { id } = useParams<{ id: string }>(); const recruitmentId = Number(id); const { isOpen, onOpen, onClose } = useDisclosure(); + const toast = useToast(); - const { data } = useFetchVolunteerDetail(recruitmentId); + const [alertModalState, setAlertModalState] = useState({ + modalTitle: '', + modalContent: '', + btnTitle: '', + onClick: () => {}, + }); - const [isRecruitmentClosed, setIsRecruitmentClosed] = useState( - data.recruitmentIsClosed, - ); + useEffect(() => { + if (!user) { + setAlertModalState({ + modalTitle: '로그인 하기', + modalContent: '로그인 하시겠습니까?', + btnTitle: '로그인 하기', + onClick: goLogInPage, + }); + } else { + setAlertModalState({ + modalTitle: '봉사 신청', + modalContent: '봉사를 신청하시겠습니까?', + btnTitle: '신청하기', + onClick: onApplyRecruitment, + }); + } + }, [user]); + + const [ + { data }, + { + data: { isAppliedRecruitment }, + }, + ] = useFetchVolunteerDetail(recruitmentId); + + const [isApplied, setIsApplied] = useState(isAppliedRecruitment); 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 volunteerDateDay = getDDay(data.recruitmentDeadline); + + const [isRecruitmentClosed, setIsRecruitmentClosed] = useState( + data.recruitmentIsClosed || volunteerDateDay < 0, + ); + + const queryClient = useQueryClient(); const { data: shelter } = useFetchSimpleShelterInfo(data.shelterId); const { mutate: applyRecruitment } = useMutation({ mutationFn: async () => await applyRecruitments(recruitmentId), onSuccess: () => { + queryClient.setQueryData(['recruitment', recruitmentId, 'isApplied'], { + isAppliedRecruitment: true, + }); + setIsApplied(true); toast({ position: 'top', description: '봉사 신청이 완료되었습니다.', @@ -77,6 +117,10 @@ export default function VolunteersDetailPage() { navigate(`/shelters/profile/${data.shelterId}`); }; + const goLogInPage = () => { + navigate('/signin'); + }; + const onApplyRecruitment = () => { onClose(); applyRecruitment(); @@ -91,17 +135,15 @@ export default function VolunteersDetailPage() { ) : ( )} {data.recruitmentTitle} - 작성일 |{' '} - {updatedAt - ? `${createFormattedTime(updatedAt)} (수정됨)` - : createFormattedTime(createdAt)} + 작성일 | {createFormattedTime(createdAt)} + {data.recruitmentUpdatedAt && ' (수정됨)'} @@ -131,7 +173,7 @@ export default function VolunteersDetailPage() { title: '마감일', content: createFormattedTime(deadline) + - `(${createWeekDayLocalString(deadline)})` + + `(${createWeekDayLocalString(deadline)}) ` + createFormattedTime(deadline, 'hh:mm'), }, ]} @@ -167,19 +209,24 @@ export default function VolunteersDetailPage() { w="100%" _active={{ bg: undefined }} _hover={{ bg: undefined }} - isDisabled={isRecruitmentClosed} + isDisabled={isRecruitmentClosed || isApplied} > - 신청하기 + {isRecruitmentClosed + ? '모집마감' + : isApplied + ? '신청완료' + : '신청하기'} - + ); } + +export default function VolunteersDetailPage() { + return ( + + + + ); +}