diff --git a/frontend/src/pages/AdminPage/tabs/ApplicationEditTab/ApplicationEditTab.tsx b/frontend/src/pages/AdminPage/tabs/ApplicationEditTab/ApplicationEditTab.tsx index 90f1d3008..12a73b6e1 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicationEditTab/ApplicationEditTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ApplicationEditTab/ApplicationEditTab.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { createApplication, updateApplication } from '@/apis/application'; @@ -10,6 +10,10 @@ import { queryKeys } from '@/constants/queryKeys'; import { useAdminClubContext } from '@/context/AdminClubContext'; import { useGetApplication } from '@/hooks/Queries/useApplication'; import QuestionBuilder from '@/pages/AdminPage/components/QuestionBuilder/QuestionBuilder'; +import { + hasErrors, + validateApplicationForm, +} from '@/pages/AdminPage/validation/validateApplicationForm'; import { PageContainer } from '@/styles/PageContainer.styles'; import { ApplicationFormData, @@ -20,14 +24,6 @@ import { import * as Styled from './ApplicationEditTab.styles'; import { QuestionDivider } from './ApplicationEditTab.styles'; -const externalApplicationUrlAllowed = [ - 'https://forms.gle', - 'https://docs.google.com/forms', - 'https://form.naver.com', - 'https://naver.me', - 'https://everytime.kr', -]; - const ApplicationEditTab = () => { const queryClient = useQueryClient(); const navigate = useNavigate(); @@ -120,6 +116,23 @@ const ApplicationEditTab = () => { const handleSubmit = async () => { if (!clubId) return; + + const validationErrors = validateApplicationForm( + formData, + applicationFormMode, + externalApplicationUrl, + ); + if (hasErrors(validationErrors)) { + const messages: string[] = [ + ...(validationErrors.title ? [validationErrors.title] : []), + ...(validationErrors.description ? [validationErrors.description] : []), + ...Object.values(validationErrors.questions ?? {}), + ...(validationErrors.externalUrl ? [validationErrors.externalUrl] : []), + ]; + alert(messages.join('\n')); + return; + } + const reorderedQuestions = formData.questions?.map((q, idx) => ({ ...q, id: idx + 1, @@ -137,17 +150,6 @@ const ApplicationEditTab = () => { if (applicationFormMode === ApplicationFormMode.INTERNAL) { payload.questions = reorderedQuestions; } else if (applicationFormMode === ApplicationFormMode.EXTERNAL) { - const isValidUrl = externalApplicationUrlAllowed.some((url) => - externalApplicationUrl.startsWith(url), - ); - - if (!isValidUrl) { - alert( - '외부 지원서 링크는 Google Forms, Naver Form 또는 Everytime 링크여야 합니다.', - ); - return; - } - payload.externalApplicationUrl = externalApplicationUrl; } @@ -186,6 +188,7 @@ const ApplicationEditTab = () => { handleFormTitleChange(e.target.value)} placeholder='지원서 제목을 입력하세요' diff --git a/frontend/src/pages/AdminPage/validation/validateApplicationForm.ts b/frontend/src/pages/AdminPage/validation/validateApplicationForm.ts new file mode 100644 index 000000000..34a725a80 --- /dev/null +++ b/frontend/src/pages/AdminPage/validation/validateApplicationForm.ts @@ -0,0 +1,71 @@ +import { ApplicationFormData, ApplicationFormMode } from '@/types/application'; + +const ALLOWED_EXTERNAL_URLS = [ + 'https://forms.gle/', + 'https://docs.google.com/forms', + 'https://form.naver.com/', + 'https://naver.me/', + 'https://everytime.kr/', +]; + +export interface ApplicationFormErrors { + title?: string; + description?: string; + questions?: Record; + externalUrl?: string; +} + +export const validateApplicationForm = ( + formData: ApplicationFormData, + mode: ApplicationFormMode, + externalUrl: string, +): ApplicationFormErrors => { + const errors: ApplicationFormErrors = {}; + + if (!formData.title?.trim()) { + errors.title = '지원서 제목을 입력해주세요.'; + } else if (formData.title.length > 50) { + errors.title = '지원서 제목은 최대 50자까지 입력할 수 있습니다.'; + } + + if (!formData.description?.trim()) { + errors.description = '지원서 설명을 입력해주세요.'; + } else if (formData.description.length > 3000) { + errors.description = '지원서 설명은 최대 3000자까지 입력할 수 있습니다.'; + } + + if (mode === ApplicationFormMode.INTERNAL) { + const questionErrors: Record = {}; + + formData.questions?.forEach((q) => { + if (!q.title.trim()) { + questionErrors[q.id] = '질문 제목을 입력해주세요.'; + } else if ( + (q.type === 'CHOICE' || q.type === 'MULTI_CHOICE') && + q.items.some((item) => !item.value.trim()) + ) { + questionErrors[q.id] = '선택지에 빈 항목이 있습니다.'; + } + }); + + if (Object.keys(questionErrors).length > 0) { + errors.questions = questionErrors; + } + } + + if (mode === ApplicationFormMode.EXTERNAL) { + if (!externalUrl.trim()) { + errors.externalUrl = '외부 지원서 링크를 입력해주세요.'; + } else if ( + !ALLOWED_EXTERNAL_URLS.some((url) => externalUrl.startsWith(url)) + ) { + errors.externalUrl = + 'Google Forms, Naver Form 또는 Everytime 링크여야 합니다.'; + } + } + + return errors; +}; + +export const hasErrors = (errors: ApplicationFormErrors): boolean => + Object.keys(errors).length > 0;