diff --git a/src/features/study/group/model/group-study-form.schema.ts b/src/features/study/group/model/group-study-form.schema.ts index ecb1fc42..70311a90 100644 --- a/src/features/study/group/model/group-study-form.schema.ts +++ b/src/features/study/group/model/group-study-form.schema.ts @@ -16,24 +16,33 @@ export type StudyClassification = (typeof STUDY_CLASSIFICATION)[number]; export const GroupStudyFormSchema = z.object({ classification: z.enum(STUDY_CLASSIFICATION), + // 스터디 유형(1) type: z.enum(STUDY_TYPES), + // 모집 대상(1) targetRoles: z .array(z.enum(TARGET_ROLE_OPTIONS)) .min(1, '역할을 1개 이상 선택해 주세요.'), + // 모집 인원(1) maxMembersCount: z .string() .trim() .regex(/^[1-9]\d*$/, '최소 1명 이상을 선택해주세요.'), + // 경력 여부(1) experienceLevels: z .array(z.enum(EXPERIENCE_LEVEL_OPTIONS)) .min(1, '경력을 1개 이상 선택해 주세요.'), + // 진행 방식(1) method: z.enum(STUDY_METHODS), + // 진행 방식 (위치) optional (1) location: z.string().trim(), + // 정기 모임(1) regularMeeting: z.enum(REGULAR_MEETINGS), + // 진행 기간 (시작일) (1) startDate: z .string() .trim() .regex(ISO_DATE_REGEX, 'YYYY-MM-DD 형식의 시작일을 입력해 주세요.'), + // 진행 시간 (종료일) (1) endDate: z .string() .trim() @@ -46,9 +55,20 @@ export const GroupStudyFormSchema = z.object({ (val) => !val || Number(val) >= 10000, '참가비는 10,000원 이상이어야 합니다.', ), + // 스터디 제목(2) title: z.string().trim().min(1, '스터디 제목을 입력해주세요.'), + // 스터디 한 줄 소개(2) summary: z.string().trim().min(1, '한 줄 소개를 입력해주세요.'), + // 스터디 소개(2) description: z.string().trim().min(1, '스터디 소개를 입력해주세요.'), + // 썸네일 START(2) + thumbnailExtension: z + .enum(THUMBNAIL_EXTENSION) + .refine((val) => val !== 'DEFAULT', '썸네일 이미지를 선택해주세요.'), + thumbnailFile: z.instanceof(File).nullable().optional(), + thumbnailUrl: z.string().nullable().optional(), + // 썸네일 END(2) + // 스터디원에게 보여줄 질문을 입력하세요(3) interviewPost: z .array(z.string()) .refine((arr) => arr.length > 0 && arr.every((v) => v.trim() !== ''), { @@ -57,11 +77,6 @@ export const GroupStudyFormSchema = z.object({ .refine((arr) => arr.length <= 10, { message: '질문은 최대 10개까지만 입력할 수 있습니다.', }), - thumbnailExtension: z - .enum(THUMBNAIL_EXTENSION) - .refine((val) => val !== 'DEFAULT', '썸네일 이미지를 선택해주세요.'), - thumbnailFile: z.instanceof(File).nullable().optional(), - thumbnailUrl: z.string().nullable().optional(), }); // 사진 상태 저장을 위한 로컬용 state diff --git a/src/features/study/group/ui/group-study-form.tsx b/src/features/study/group/ui/group-study-form.tsx index c728f9c2..11afb6bf 100644 --- a/src/features/study/group/ui/group-study-form.tsx +++ b/src/features/study/group/ui/group-study-form.tsx @@ -1,5 +1,5 @@ import { zodResolver } from '@hookform/resolvers/zod'; -import { createContext, useContext, useState } from 'react'; +import { createContext, useContext, useMemo, useState } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import Button from '@/components/ui/button'; import { Modal } from '@/components/ui/modal'; @@ -68,6 +68,48 @@ export default function GroupStudyForm({ if (step > 1) setStep((s) => (s - 1) as 1 | 2 | 3); }; + const currentValues = watch(); + + const isNextButtonDisabled = useMemo(() => { + const currentStepFields = STEP_FIELDS[step]; + + return currentStepFields.some((field) => { + const value = currentValues[field]; + const error = formState.errors[field]; + + // 에러가 있으면 비활성화 + if (error) return true; + + // 필수 필드가 비어있으면 비활성화 + if (field === 'targetRoles' || field === 'experienceLevels') { + return !value || (Array.isArray(value) && value.length === 0); + } + if ( + field === 'maxMembersCount' || + field === 'startDate' || + field === 'endDate' || + field === 'title' || + field === 'summary' || + field === 'description' + ) { + return !value || (typeof value === 'string' && value.trim() === ''); + } + if (field === 'thumbnailExtension') { + return !value || value === 'DEFAULT'; + } + if (field === 'interviewPost') { + return ( + !value || + !Array.isArray(value) || + value.length === 0 || + value.some((q) => !q || q.trim() === '') + ); + } + + return false; + }); + }, [step, currentValues, formState.errors]); + return ( @@ -113,6 +155,7 @@ export default function GroupStudyForm({ color="primary" type="button" onClick={goNext} + disabled={isNextButtonDisabled} > 다음