33import { zodResolver } from '@hookform/resolvers/zod' ;
44import { useRouter } from 'next/navigation' ;
55import { useForm } from 'react-hook-form' ;
6+ import { toast } from 'sonner' ;
67import z from 'zod' ;
78
89import BannerImageUpload from '@/features/activity-registration/components/banner-image-upload' ;
@@ -15,14 +16,10 @@ import {
1516} from '@/features/activity-registration/libs/constants/formOption' ;
1617import { useRegistrationMutation } from '@/features/activity-registration/libs/hooks/useRegistrationMutation' ;
1718import { FormInput } from '@/shared/components/form-input/form-input' ;
18- import Modal from '@/shared/components/modal/components' ;
19- import { useModalStore } from '@/shared/libs/stores/useModalStore' ;
20- import {
21- ActivityRegistrationFormData ,
22- ActivityRegistrationParams ,
23- } from '@/shared/types/activity' ;
19+ import { ActivityRegistrationParams } from '@/shared/types/activity' ;
20+
21+ // 기존 hasDuplicateStartTime 함수를 사용
2422
25- // 업데이트된 스키마 - schedules 배열로 변경
2623const registerSchema = z . object ( {
2724 title : z . string ( ) . min ( 1 , { message : '제목을 입력해 주세요.' } ) ,
2825 category : z . string ( ) . min ( 1 , { message : '카테고리를 선택해 주세요.' } ) ,
@@ -39,29 +36,33 @@ const registerSchema = z.object({
3936 schedules : z
4037 . array (
4138 z . object ( {
42- date : z . string ( ) . min ( 1 , { message : '날짜를 선택해 주세요.' } ) ,
39+ date : z . string ( ) . min ( 1 , {
40+ message : '날짜를 선택해 주세요.' ,
41+ } ) ,
4342 startTime : z . string ( ) . min ( 1 , { message : '시작 시간을 선택해 주세요.' } ) ,
4443 endTime : z . string ( ) . min ( 1 , { message : '종료 시간을 선택해 주세요.' } ) ,
4544 } ) ,
4645 )
47- . min ( FORM_CONSTRAINTS . SCHEDULES . MIN_COUNT , {
48- message : '최소 하나의 시간대를 등록해 주세요.' ,
49- } ) ,
46+ . min ( 1 , { message : '예약 가능한 시간대는 최소 1개 이상 등록해주세요.' } ) ,
5047 bannerImages : z . string ( ) . min ( 1 , { message : '배너 이미지를 등록해 주세요.' } ) ,
5148 subImages : z
5249 . array ( z . string ( ) )
5350 . min ( 1 , { message : '소개 이미지를 등록해 주세요.' } ) ,
5451} ) ;
5552
53+ type FormData = z . infer < typeof registerSchema > ;
54+
5655const ActivityRegistrationForm = ( ) => {
5756 const {
5857 register,
5958 handleSubmit,
6059 watch,
6160 setValue,
61+ trigger,
6262 formState : { errors } ,
63- } = useForm < ActivityRegistrationFormData > ( {
63+ } = useForm < FormData > ( {
6464 resolver : zodResolver ( registerSchema ) ,
65+ mode : 'onChange' ,
6566 defaultValues : {
6667 schedules : [ ] ,
6768 bannerImages : '' ,
@@ -70,10 +71,11 @@ const ActivityRegistrationForm = () => {
7071 } ) ;
7172
7273 const registrationMutation = useRegistrationMutation ( ) ;
73- const { openModal, closeModal } = useModalStore ( ) ;
7474 const router = useRouter ( ) ;
7575
76- const onSubmit = ( data : ActivityRegistrationFormData ) => {
76+ const onSubmit = async ( data : FormData ) => {
77+ console . log ( '폼 제출 시작:' , data ) ; // 디버깅용
78+
7779 // 모든 스케줄의 시간 유효성 검증
7880 const hasInvalidSchedules = data . schedules . some ( ( schedule ) => {
7981 const startIndex = TIME_OPTIONS . findIndex (
@@ -90,30 +92,47 @@ const ActivityRegistrationForm = () => {
9092 return ;
9193 }
9294
95+ // 중복 시간 체크는 DateScheduler에서 처리되므로 제거
96+
9397 // API 데이터 변환
9498 const apiData : ActivityRegistrationParams = {
9599 title : data . title ,
96100 category : data . category ,
97101 description : data . description ,
98102 address : data . address ,
99- price : data . price ,
103+ price : data . price as number ,
100104 bannerImageUrl : data . bannerImages ,
101105 schedules : data . schedules ,
102106 subImageUrls : data . subImages ,
103107 } ;
104108
109+ console . log ( 'API 데이터:' , apiData ) ; // 디버깅용
110+
105111 // TanStack Query mutation 실행
106- registrationMutation . mutate ( apiData , {
107- onSuccess : ( ) => {
108- openModal ( ) ;
109- } ,
110- } ) ;
112+ try {
113+ await registrationMutation . mutateAsync ( apiData ) ;
114+ console . log ( '등록 성공!' ) ; // 디버깅용
115+ toast . success ( '체험이 등록되었습니다!' ) ;
116+ router . push ( '/activities' ) ;
117+ } catch ( error ) {
118+ console . error ( '등록 실패:' , error ) ; // 디버깅용
119+ toast . error ( '등록 중 오류가 발생했습니다. 다시 시도해주세요.' ) ;
120+ }
111121 } ;
112122
113123 return (
114124 < form
115125 onSubmit = { handleSubmit ( onSubmit ) }
116126 className = "mt-[2.4rem] flex flex-col"
127+ onKeyDown = { ( e ) => {
128+ if ( e . key === 'Enter' && e . target instanceof HTMLInputElement ) {
129+ const target = e . target as HTMLInputElement ;
130+ if ( target . type === 'button' || target . readOnly ) {
131+ e . preventDefault ( ) ;
132+ e . stopPropagation ( ) ;
133+ }
134+ }
135+ } }
117136 >
118137 { /* 제목 */ }
119138 < FormInput
@@ -166,33 +185,46 @@ const ActivityRegistrationForm = () => {
166185 label = "주소"
167186 name = "address"
168187 register = { register }
188+ setValue = { setValue }
189+ watch = { watch }
169190 error = { errors . address }
170- inputType = "input "
191+ inputType = "address "
171192 placeholder = "주소를 입력해 주세요"
172193 required
173194 />
174195
175196 { /* DateScheduler 컴포넌트 사용 */ }
176197 < DateScheduler
177198 schedules = { watch ( 'schedules' ) || [ ] }
178- onChange = { ( schedules ) => setValue ( 'schedules' , schedules ) }
199+ onChange = { async ( schedules ) => {
200+ setValue ( 'schedules' , schedules ) ;
201+ await trigger ( 'schedules' ) ;
202+ } }
179203 errors = { { schedules : errors . schedules } }
204+ register = { register }
205+ formErrors = { errors }
180206 />
181207
182208 < BannerImageUpload
183209 label = "배너 이미지 등록"
184210 name = "bannerImages"
185211 error = { errors . bannerImages }
186212 required
187- onChange = { ( imageUrl : string ) => setValue ( 'bannerImages' , imageUrl ) }
213+ onChange = { async ( imageUrl : string ) => {
214+ setValue ( 'bannerImages' , imageUrl ) ;
215+ await trigger ( 'bannerImages' ) ;
216+ } }
188217 value = { watch ( 'bannerImages' ) || '' }
189218 />
190219
191220 < SubImage
192221 label = "소개 이미지 등록"
193222 name = "subImages"
194223 error = { errors . subImages }
195- onChange = { ( imageUrls : string [ ] ) => setValue ( 'subImages' , imageUrls ) }
224+ onChange = { async ( imageUrls : string [ ] ) => {
225+ setValue ( 'subImages' , imageUrls ) ;
226+ await trigger ( 'subImages' ) ;
227+ } }
196228 value = { watch ( 'subImages' ) || [ ] }
197229 />
198230
@@ -201,27 +233,14 @@ const ActivityRegistrationForm = () => {
201233 < button
202234 type = "submit"
203235 disabled = { registrationMutation . isPending }
204- className = { `h-[4.1rem] w-[16rem] rounded-[1.2rem] text-center text-[1.4rem] font-bold text-white md:h-[4.8rem] md:w-[16rem] md:rounded-[1.6rem] md:text-[1.6rem] lg:h-[5.2rem] lg:w-[18rem] ${
236+ className = { `h-[4.1rem] w-[16rem] cursor-pointer rounded-[1.2rem] text-center text-[1.4rem] font-bold text-white md:h-[4.8rem] md:w-[16rem] md:rounded-[1.6rem] md:text-[1.6rem] lg:h-[5.2rem] lg:w-[18rem] ${
205237 registrationMutation . isPending
206238 ? 'cursor-not-allowed bg-gray-400'
207239 : 'bg-main hover:bg-blue-500'
208240 } `}
209241 >
210242 { registrationMutation . isPending ? '등록 중...' : '체험 등록하기' }
211243 </ button >
212- < Modal type = "confirm" >
213- < Modal . Header > 체험 등록이 완료되었습니다.</ Modal . Header >
214- < Modal . Button
215- color = "blue"
216- ariaLabel = "확인 버튼"
217- onClick = { ( ) => {
218- closeModal ( ) ;
219- router . push ( '/activities' ) ;
220- } }
221- >
222- 확인
223- </ Modal . Button >
224- </ Modal >
225244 </ div >
226245 </ form >
227246 ) ;
0 commit comments