11import { zodResolver } from '@hookform/resolvers/zod' ;
2+ import { uploadImage } from '@shared/apis/image' ;
3+ import { useCreateSellPostMutation , useUpdateSellPostMutation } from '@shared/apis/sell' ;
24import { ROUTES } from '@shared/constants' ;
35import { useClickOutside , useToast } from '@shared/hooks' ;
4- import { validateImageFile } from '@shared/utils' ;
5- import { MAX_IMAGE_BYTES , sellSchema , type SellFormData } from '@shared/utils/schemas' ;
6- import { useEffect , useRef , useState } from 'react' ;
6+ import { sellSchema , type SellFormData } from '@shared/utils/schemas' ;
7+ import { useEffect , useMemo , useRef , useState } from 'react' ;
78import { useForm } from 'react-hook-form' ;
89import { useLocation , useNavigate } from 'react-router' ;
10+ import { buildSellPostRequest } from './buildSellPostRequest' ;
911import { getSellFormDefaults , mapSellDraftToForm } from './initialValues' ;
12+ import { useSellImage } from './useSellImage' ;
1013import type { SellState } from '@shared/types/sell' ;
1114
1215export const useSellForm = ( ) => {
1316 const navigate = useNavigate ( ) ;
1417 const location = useLocation ( ) ;
1518 const { showToast } = useToast ( ) ;
19+ const createSellPostMutation = useCreateSellPostMutation ( ) ;
20+ const locationState = useMemo ( ( ) => ( location . state ?? { } ) as SellState , [ location . state ] ) ;
21+ const editPostId = locationState . postId ?? null ;
22+ const existingImageUrl = locationState . imageUrl ?? null ;
23+ const updateSellPostMutation = useUpdateSellPostMutation ( editPostId ?? '' ) ;
1624
1725 const {
1826 control,
@@ -29,7 +37,6 @@ export const useSellForm = () => {
2937 } ) ;
3038
3139 const hasInitialized = useRef ( false ) ;
32- const [ previewUrl , setPreviewUrl ] = useState < string | null > ( null ) ;
3340 const [ isDropdownOpen , setIsDropdownOpen ] = useState ( false ) ;
3441 const dropdownRef = useRef < HTMLDivElement | null > ( null ) ;
3542
@@ -40,31 +47,12 @@ export const useSellForm = () => {
4047 const screenCondition = watch ( 'screenCondition' ) ;
4148 const batteryCondition = watch ( 'batteryCondition' ) ;
4249
43- const handleImageChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
44- const file = e . target . files ?. [ 0 ] ;
45- if ( ! file ) {
46- return ;
47- }
48-
49- const validation = validateImageFile ( file , MAX_IMAGE_BYTES ) ;
50- if ( ! validation . ok ) {
51- showToast ( validation . message ) ;
52- setPreviewUrl ( null ) ;
53- resetField ( 'imageFile' ) ;
54- setError ( 'imageFile' , {
55- type : 'validate' ,
56- message : validation . message ,
57- } ) ;
58- return ;
59- }
60-
61- const reader = new FileReader ( ) ;
62- reader . onload = ( ) => {
63- setPreviewUrl ( reader . result as string ) ;
64- } ;
65- reader . readAsDataURL ( file ) ;
66- setValue ( 'imageFile' , file , { shouldValidate : true } ) ;
67- } ;
50+ const { previewUrl, setPreviewUrl, handleImageChange } = useSellImage ( {
51+ showToast,
52+ setError,
53+ resetField,
54+ setValue,
55+ } ) ;
6856
6957 const closeDropdown = ( ) => {
7058 setIsDropdownOpen ( false ) ;
@@ -94,29 +82,63 @@ export const useSellForm = () => {
9482 if ( hasInitialized . current ) {
9583 return ;
9684 }
97- const state = ( location . state ?? { } ) as SellState ;
98- if ( Object . keys ( state ) . length === 0 ) {
85+ if ( Object . keys ( locationState ) . length === 0 ) {
9986 return ;
10087 }
101- reset ( mapSellDraftToForm ( state ) ) ;
102- if ( state . imageFile ) {
103- setValue ( 'imageFile' , state . imageFile , { shouldValidate : true } ) ;
88+ reset ( mapSellDraftToForm ( locationState ) ) ;
89+ if ( locationState . imageFile ) {
90+ setValue ( 'imageFile' , locationState . imageFile , { shouldValidate : true } ) ;
10491 } else {
10592 resetField ( 'imageFile' ) ;
10693 }
107- setPreviewUrl ( state . imageUrl ?? null ) ;
94+ setPreviewUrl ( locationState . imageUrl ?? null ) ;
10895 hasInitialized . current = true ;
109- } , [ location . state , reset , resetField , setValue ] ) ;
110-
111- const onSubmit = handleSubmit ( ( data ) => {
112- // API 추후 연결
113- showToast ( '등록되었습니다' , 'success' ) ;
114- navigate ( ROUTES . SELL_CONFIRM , {
115- state : {
116- ...data ,
117- imageUrl : previewUrl ,
118- } ,
119- } ) ;
96+ } , [ locationState , reset , resetField , setValue , setPreviewUrl ] ) ;
97+
98+ const onSubmit = handleSubmit ( async ( data ) => {
99+ try {
100+ const imageFile = data . imageFile ;
101+ if ( ! imageFile && ! existingImageUrl ) {
102+ const message = '이미지를 업로드해 주세요.' ;
103+ showToast ( message ) ;
104+ setError ( 'imageFile' , { type : 'validate' , message } ) ;
105+ return ;
106+ }
107+
108+ let imageUrl = existingImageUrl ?? '' ;
109+ if ( imageFile ) {
110+ const uploaded = await uploadImage ( 'PRODUCT' , imageFile ) ;
111+ imageUrl = uploaded . fileUrl ;
112+ }
113+
114+ const request = buildSellPostRequest ( data , imageUrl ) ;
115+
116+ if ( editPostId ) {
117+ await updateSellPostMutation . mutateAsync ( request ) ;
118+ showToast ( '수정되었습니다' , 'success' ) ;
119+ } else {
120+ const created = await createSellPostMutation . mutateAsync ( request ) ;
121+ showToast ( '등록되었습니다' , 'success' ) ;
122+ navigate ( ROUTES . SELL_CONFIRM , {
123+ state : {
124+ ...data ,
125+ postId : created . id ,
126+ imageUrl,
127+ } ,
128+ } ) ;
129+ return ;
130+ }
131+
132+ navigate ( ROUTES . SELL_CONFIRM , {
133+ state : {
134+ ...data ,
135+ postId : editPostId ,
136+ imageUrl,
137+ } ,
138+ } ) ;
139+ } catch {
140+ showToast ( '판매글 처리 실패. 다시 시도해 주세요.' , 'error' ) ;
141+ }
120142 } ) ;
121143
122144 return {
0 commit comments