diff --git a/src/apis/recruit.js b/src/apis/recruit.js index fc72434..aa2e5e1 100644 --- a/src/apis/recruit.js +++ b/src/apis/recruit.js @@ -7,6 +7,12 @@ import instance from './instance'; */ export const submitRecruit = (data) => instance.post('/api/v1/visitor/applicantMembers', data); +/** + * 신입 부원 모집 공고 조회 + * @returns {Promise} + */ +export const getMainRecruitment = () => instance.get('/api/v1/visitor/main/recruitment'); + /** * (관리자) 지원자 목록 조회 * @returns {Promise} diff --git a/src/components/recruit/RecruitCompleteCard.jsx b/src/components/recruit/RecruitCompleteCard.jsx index c81b91a..c0f7d4b 100644 --- a/src/components/recruit/RecruitCompleteCard.jsx +++ b/src/components/recruit/RecruitCompleteCard.jsx @@ -1,8 +1,81 @@ +import { useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; +import { getMainRecruitment } from '@/apis/recruit'; import { ROUTES } from '@/constants/routes'; -function RecruitCompleteCard() { +const COMPLETE_MESSAGE = '회장의 연락을 통해 면접 일정이 조정될 예정이오니, 잠시 기다려 주세요.'; + +const EMPTY_RECRUITMENT_NOTICE = '모집 공고 정보를 불러오지 못했습니다.'; + +const formatKoreanDate = (dateText, shouldIncludeYear = true) => { + if (typeof dateText !== 'string') return ''; + + const [year, month, day] = dateText.split('-'); + if (!year || !month || !day) return dateText; + + const formattedMonth = Number(month); + const formattedDay = Number(day); + if (Number.isNaN(formattedMonth) || Number.isNaN(formattedDay)) return dateText; + + const monthDayText = `${formattedMonth}월 ${formattedDay}일`; + + return shouldIncludeYear ? `${year}년 ${monthDayText}` : monthDayText; +}; + +const createDateRangeText = (startDate, endDate) => { + if (typeof startDate !== 'string' && typeof endDate !== 'string') return ''; + if (typeof startDate !== 'string') return formatKoreanDate(endDate); + if (typeof endDate !== 'string' || startDate === endDate) return formatKoreanDate(startDate); + + const startYear = startDate.split('-')[0]; + const endYear = endDate.split('-')[0]; + const shouldShowEndYear = startYear !== endYear; + + return `${formatKoreanDate(startDate)} ~ ${formatKoreanDate(endDate, shouldShowEndYear)}`; +}; + +const RecruitCompleteCard = () => { + const [recruitment, setRecruitment] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [hasRecruitmentError, setHasRecruitmentError] = useState(false); + + useEffect(() => { + let isMounted = true; + + const fetchMainRecruitment = async () => { + try { + const response = await getMainRecruitment(); + if (!isMounted) return; + + setRecruitment(response.data?.data ?? null); + setHasRecruitmentError(false); + } catch (error) { + if (!isMounted) return; + + console.error('[RecruitCompleteCard] 모집 공고 조회 실패', error); + setHasRecruitmentError(true); + } finally { + if (isMounted) { + setIsLoading(false); + } + } + }; + + fetchMainRecruitment(); + + return () => { + isMounted = false; + }; + }, []); + + const interviewPeriod = createDateRangeText( + recruitment?.interviewStart, + recruitment?.interviewEnd + ); + const notificationDate = formatKoreanDate(recruitment?.notificationDate); + const contactText = [recruitment?.bossName, recruitment?.contactNumber].filter(Boolean).join(' '); + return (
@@ -12,26 +85,42 @@ function RecruitCompleteCard() {
-

- 면접 기간: - 2026년 4월 6일 ~ 4월 7일 -

-

- 합격 통보: - 2026년 4월 7일 예정{' '} - - (해당 일정은 변동될 수 있습니다.) - -

-

- 면접 장소: - 3-511호 ONE 동아리방 -

- -

- 회장의 연락을 통해 면접 일정이 조정될 예정이오니, 잠시 기다려 주세요. -

-

문의: 회장 최예은 010-1111-1111

+ {isLoading &&

모집 공고 정보를 불러오는 중입니다.

} + + {!isLoading && hasRecruitmentError && ( +

+ {EMPTY_RECRUITMENT_NOTICE} +

+ )} + + {!isLoading && !hasRecruitmentError && recruitment && ( + <> + {interviewPeriod && ( +

+ 면접 기간: + {interviewPeriod} +

+ )} + {notificationDate && ( +

+ 합격 통보: + {notificationDate} 예정{' '} + + (해당 일정은 변동될 수 있습니다.) + +

+ )} + {recruitment.roomLocation && ( +

+ 면접 장소: + {recruitment.roomLocation} +

+ )} + + )} + +

{COMPLETE_MESSAGE}

+ {contactText &&

문의: 회장 {contactText}

}
); -} +}; export default RecruitCompleteCard; diff --git a/src/hooks/useRecruitForm.js b/src/hooks/useRecruitForm.js index f424a96..39e340e 100644 --- a/src/hooks/useRecruitForm.js +++ b/src/hooks/useRecruitForm.js @@ -18,6 +18,12 @@ const FIELD_VALUE_FORMATTERS = { const DEFAULT_FIELD_VALUE_FORMATTER = (value) => value; +const DEFAULT_SUBMIT_ERROR_MESSAGE = '모집 신청 제출에 실패했습니다. 잠시 후 다시 시도해 주세요.'; + +const getRecruitSubmitErrorMessage = (error) => { + return error.response?.data?.message ?? DEFAULT_SUBMIT_ERROR_MESSAGE; +}; + export function useRecruitForm() { const navigate = useNavigate(); const isMountedRef = useRef(true); @@ -29,6 +35,8 @@ export function useRecruitForm() { const [isSubmitting, setIsSubmitting] = useState(false); useEffect(() => { + isMountedRef.current = true; + return () => { isMountedRef.current = false; }; @@ -73,18 +81,24 @@ export function useRecruitForm() { setIsSubmitting(true); setSubmitError(''); try { - await submitRecruit({ ...form, grade: Number(form.grade) }); + const response = await submitRecruit({ ...form, grade: Number(form.grade) }); if (!isMountedRef.current) return; + if (response.data?.success === false) { + const errorMessage = response.data?.message ?? DEFAULT_SUBMIT_ERROR_MESSAGE; + setIsConfirmOpen(false); + setSubmitError(errorMessage); + return; + } + setIsConfirmOpen(false); navigate(ROUTES.RECRUIT_COMPLETE); } catch (error) { if (!isMountedRef.current) return; setIsConfirmOpen(false); - setSubmitError( - error.response?.data?.message || '모집 신청 제출에 실패했습니다. 잠시 후 다시 시도해 주세요.' - ); + const errorMessage = getRecruitSubmitErrorMessage(error); + setSubmitError(errorMessage); } finally { if (isMountedRef.current) { setIsSubmitting(false);