11import React , { useEffect , useState } from 'react'
22import { Row , Col , Form , Button } from 'react-bootstrap'
33import { PcDropdownSelect , PcCompanyLogoUploader , PcInput } from '../ui'
4- import { ROUTES , STEPS } from './constants'
4+ import { EMPTY_ENTITIES , INPUT_VALIDATORS , ROUTES , STEPS } from './constants'
55import { fetchCustomers , fetchQuotes } from '../services'
66import { useAppHooks } from '../hooks'
77import { extractNames } from '../utils'
88
99export const CustomerForm = ( ) => {
10- const defaultCustomer = {
11- company_name : '' ,
12- full_name : '' ,
13- email : '' ,
14- position : '' ,
15- address : '' ,
16- notes : '' ,
17- logo_file : null ,
18- logo_url : null ,
19- }
20-
2110 const [ customers , setCustomers ] = useState ( [ ] )
22- const [ customer , setCustomer ] = useState ( defaultCustomer )
23-
11+ const [ customer , setCustomer ] = useState ( EMPTY_ENTITIES . customer )
12+ const [ isNextDisabled , setIsNextDisabled ] = useState ( true )
2413 const [ errors , setErrors ] = useState ( { } )
2514
2615 const { navigate } = useAppHooks ( )
@@ -36,34 +25,36 @@ export const CustomerForm = () => {
3625 label : customer . attributes . company_name ,
3726 } ) )
3827
39- const validateForm = ( ) => {
40- const newErrors = { }
41-
42- if ( ! customer . company_name . trim ( ) ) {
43- newErrors . company_name = 'Company name is required'
44- }
45-
46- if ( ! ! customer . email && ! / ^ [ ^ \s @ ] + @ [ ^ \s @ ] + \. [ ^ \s @ ] + $ / . test ( customer . email ) ) {
47- newErrors . email = 'Invalid email format'
48- }
49-
50- setErrors ( newErrors )
51-
52- return Object . keys ( newErrors ) . length === 0
53- }
28+ const selectedCompany =
29+ customers . find ( ( c ) => c . attributes . company_name . toLowerCase ( ) === customer . company_name . toLowerCase ( ) ) ?. id ||
30+ customer . company_name
5431
5532 const handleCompanyChange = ( e ) => {
56- const value = e . target . value
33+ const { value } = e . target
5734 const selectedCustomer = customers . find ( ( customer ) => customer . id === value )
5835
5936 if ( selectedCustomer ) {
6037 setCustomer ( selectedCustomer . attributes )
6138 } else {
6239 setCustomer ( {
63- ...defaultCustomer ,
40+ ...EMPTY_ENTITIES . customer ,
6441 company_name : value ,
6542 } )
6643 }
44+
45+ setErrors ( ( prev ) => ( { ...prev , company_name : '' } ) )
46+ }
47+
48+ const handleCompanyInputChange = ( e ) => {
49+ const { value } = e . target
50+
51+ if ( value ) {
52+ setIsNextDisabled ( false )
53+ setErrors ( ( prev ) => ( { ...prev , company_name : '' } ) )
54+ } else {
55+ setIsNextDisabled ( true )
56+ setErrors ( ( prev ) => ( { ...prev , company_name : 'Company name is required' } ) )
57+ }
6758 }
6859
6960 const handleInputChange = ( e ) => {
@@ -76,73 +67,92 @@ export const CustomerForm = () => {
7667 setErrors ( ( prev ) => ( { ...prev , [ id ] : '' } ) )
7768 }
7869
79- const handleChangeFullName = ( e ) => {
70+ const handleEmailChange = ( e ) => {
8071 const { value } = e . target
81- setCustomer ( {
82- ...customer ,
83- ...extractNames ( value ) ,
84- full_name : value ,
85- } )
72+
73+ if ( value && ! INPUT_VALIDATORS . email . test ( value ) ) {
74+ setIsNextDisabled ( true )
75+ setErrors ( ( prev ) => ( { ...prev , email : 'Invalid email format' } ) )
76+ } else {
77+ setIsNextDisabled ( false )
78+ setErrors ( ( prev ) => ( { ...prev , email : '' } ) )
79+ }
80+
81+ setCustomer ( ( prev ) => ( { ...prev , email : value } ) )
8682 }
8783
88- const handleChangeLogo = ( e ) => {
89- const file = e . target . files [ 0 ]
84+ const handleFullNameChange = ( e ) => {
85+ const { value } = e . target
9086
9187 setCustomer ( ( prev ) => ( {
9288 ...prev ,
93- logo_file : file || null ,
94- logo_url : file ? URL . createObjectURL ( file ) : null ,
89+ ... extractNames ( value ) ,
90+ full_name : value ,
9591 } ) )
96-
97- setErrors ( ( prev ) => ( { ...prev , logo : '' } ) )
9892 }
9993
100- const handleNext = ( e ) => {
101- e . preventDefault ( )
94+ const handleLogoChange = ( e ) => {
95+ const file = e . target . files [ 0 ]
10296
103- if ( ! validateForm ( ) ) {
97+ if ( ! file ) {
98+ setErrors ( ( prev ) => ( { ...prev , logo : '' } ) )
10499 return
105100 }
106101
107- const formData = new FormData ( )
108- const { first_name, last_name } = extractNames ( customer . full_name )
109-
110- if ( customer . logo_file ) formData . append ( 'customer[logo]' , customer . logo_file )
111- formData . append ( 'customer[company_name]' , customer . company_name )
112- formData . append ( 'customer[first_name]' , first_name )
113- formData . append ( 'customer[last_name]' , last_name )
114- formData . append ( 'customer[position]' , customer . position )
115- formData . append ( 'customer[email]' , customer . email )
116- formData . append ( 'customer[address]' , customer . address )
117- formData . append ( 'customer[notes]' , customer . notes )
118-
119- fetchCustomers . upsert ( formData )
120- . then ( async ( response ) => {
121- const { data : customerData } = response
122-
123- if ( ! customers . some ( ( c ) => c . id === customerData . id ) ) {
124- setCustomers ( ( prev ) => [ ...prev , customerData ] )
125- }
126-
127- const { data : quoteData } = await fetchQuotes . create ( {
128- quote : {
129- customer_id : customerData . id ,
130- total_price : 0 ,
131- step : STEPS . ITEM_PRICING ,
132- } ,
133- } )
134-
135- navigate ( `${ ROUTES . ITEM_PRICING } ?quote_id=${ quoteData . id } ` )
136- } ) . catch ( ( error ) => {
137- setErrors ( prev => ( { ...prev , logo : error . response . data . errors } ) )
138- } )
102+ const logoErrors = [ ]
103+
104+ if ( file . size > INPUT_VALIDATORS . maxSizeFile ) {
105+ logoErrors . push ( 'Logo must be less than 2MB' )
106+ }
107+
108+ if ( ! INPUT_VALIDATORS . fileType . includes ( file . type ) ) {
109+ logoErrors . push ( 'Logo must be a JPEG or PNG file' )
110+ }
111+
112+ if ( logoErrors . length > 0 ) {
113+ setIsNextDisabled ( true )
114+ setErrors ( ( prev ) => ( { ...prev , logo : logoErrors . join ( '\n' ) } ) )
115+ } else {
116+ setIsNextDisabled ( false )
117+ setCustomer ( ( prev ) => ( {
118+ ...prev ,
119+ logo_file : file ,
120+ logo_url : URL . createObjectURL ( file ) ,
121+ } ) )
122+
123+ setErrors ( ( prev ) => ( { ...prev , logo : '' } ) )
124+ }
139125 }
140126
141- if ( ! customers ) return null
127+ const handleNext = async ( e ) => {
128+ e . preventDefault ( )
142129
143- const selectedCompany =
144- customers . find ( ( c ) => c . attributes . company_name . toLowerCase ( ) === customer . company_name . toLowerCase ( ) ) ?. id ||
145- customer . company_name
130+ setIsNextDisabled ( true ) // disable next button while form is being submitted
131+
132+ try {
133+ const { data : customerData } = await fetchCustomers . upsertUseFormData ( customer )
134+
135+ if ( ! customers . some ( ( c ) => c . id === customerData . id ) ) {
136+ setCustomers ( ( prev ) => [ ...prev , customerData ] )
137+ }
138+
139+ const { data : quoteData } = await fetchQuotes . create ( {
140+ quote : {
141+ customer_id : customerData . id ,
142+ total_price : 0 ,
143+ step : STEPS . ITEM_PRICING ,
144+ } ,
145+ } )
146+
147+ navigate ( `${ ROUTES . ITEM_PRICING } ?quote_id=${ quoteData . id } ` )
148+ } catch ( error ) {
149+ const logoErrors = error ?. response ?. data ?. errors || { errors : [ ] }
150+
151+ setErrors ( prev => ( { ...prev , ...logoErrors } ) )
152+ } finally {
153+ setIsNextDisabled ( false ) // enable next button
154+ }
155+ }
146156
147157 return (
148158 < Form onSubmit = { handleNext } className = { 'd-flex flex-column w-100 align-items-center' } >
@@ -152,7 +162,8 @@ export const CustomerForm = () => {
152162 < Col className = { 'image-placeholder' } >
153163 < PcCompanyLogoUploader
154164 id = "company_logo"
155- onChange = { handleChangeLogo }
165+ onChange = { handleLogoChange }
166+ accept = { INPUT_VALIDATORS . fileType . join ( ',' ) }
156167 logo = { customer . logo_url }
157168 error = { errors . logo } />
158169 </ Col >
@@ -172,7 +183,7 @@ export const CustomerForm = () => {
172183 value = { selectedCompany }
173184 error = { errors . company_name }
174185 onChange = { handleCompanyChange }
175- onInputChange = { handleInputChange }
186+ onInputChange = { handleCompanyInputChange }
176187 hasIcon = { true }
177188 />
178189 </ Col >
@@ -186,7 +197,7 @@ export const CustomerForm = () => {
186197 label = "Client"
187198 height = "42px"
188199 value = { customer . full_name }
189- onChange = { handleChangeFullName }
200+ onChange = { handleFullNameChange }
190201 />
191202 </ Col >
192203 < Col className = "title-input" >
@@ -215,7 +226,7 @@ export const CustomerForm = () => {
215226 height = "42px"
216227 value = { customer . email }
217228 error = { errors . email }
218- onChange = { handleInputChange }
229+ onChange = { handleEmailChange }
219230 />
220231 </ Col >
221232 < Col >
@@ -244,7 +255,7 @@ export const CustomerForm = () => {
244255 </ Col >
245256 </ Row >
246257 </ div >
247- < Button type = { 'submit' } className = "pc-btn-next" disabled = { ! customer . company_name } >
258+ < Button type = { 'submit' } className = "pc-btn-next" disabled = { isNextDisabled } >
248259 Next
249260 </ Button >
250261 </ Form >
0 commit comments