1- import React , { useState , useRef , useCallback } from 'react' ;
1+ import React , { useState , useRef , useCallback , useEffect } from 'react' ;
22import { Send , Rocket , Linkedin } from 'lucide-react' ;
33import emailjs from '@emailjs/browser' ;
4- import { emailConfig } from '../../config' ;
4+ import { emailConfig , RATE_LIMIT_MS } from '../../config' ;
55
66interface FormState {
77 name : string ;
@@ -10,6 +10,35 @@ interface FormState {
1010 message : string ;
1111}
1212
13+ interface FormErrors {
14+ name ?: string ;
15+ email ?: string ;
16+ message ?: string ;
17+ }
18+
19+ const validateEmail = ( email : string ) : boolean => {
20+ const re = / ^ [ a - z A - Z 0 - 9 . _ % + - ] + @ [ a - z A - Z 0 - 9 . - ] + \. [ a - z A - Z ] { 2 , } $ / ;
21+ return re . test ( email ) ;
22+ } ;
23+
24+ const validateForm = ( formState : FormState ) : FormErrors => {
25+ const errors : FormErrors = { } ;
26+
27+ if ( formState . name . length < 2 ) {
28+ errors . name = 'Name must be at least 2 characters long' ;
29+ }
30+
31+ if ( ! validateEmail ( formState . email ) ) {
32+ errors . email = 'Please enter a valid email address' ;
33+ }
34+
35+ if ( formState . message . length < 10 ) {
36+ errors . message = 'Message must be at least 10 characters long' ;
37+ }
38+
39+ return errors ;
40+ } ;
41+
1342const ContactSection : React . FC = ( ) => {
1443 const form = useRef < HTMLFormElement > ( null ) ;
1544 const [ formState , setFormState ] = useState < FormState > ( {
@@ -21,46 +50,62 @@ const ContactSection: React.FC = () => {
2150
2251 const [ isSubmitting , setIsSubmitting ] = useState ( false ) ;
2352 const [ isSubmitted , setIsSubmitted ] = useState ( false ) ;
53+ const [ error , setError ] = useState < string | null > ( null ) ;
54+ const [ formErrors , setFormErrors ] = useState < FormErrors > ( { } ) ;
55+ const [ lastSubmissionTime , setLastSubmissionTime ] = useState < number > ( 0 ) ;
2456
2557 const handleChange = useCallback ( ( e : React . ChangeEvent < HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement > ) => {
2658 const { name, value } = e . target ;
2759 setFormState ( prev => ( { ...prev , [ name ] : value } ) ) ;
60+ // Clear errors when user starts typing
61+ setFormErrors ( prev => ( { ...prev , [ name ] : undefined } ) ) ;
2862 } , [ ] ) ;
2963
30- const handleSubmit = useCallback ( ( e : React . FormEvent ) => {
64+ const handleSubmit = useCallback ( async ( e : React . FormEvent ) => {
3165 e . preventDefault ( ) ;
66+ setError ( null ) ;
67+
68+ // Validate form
69+ const errors = validateForm ( formState ) ;
70+ if ( Object . keys ( errors ) . length > 0 ) {
71+ setFormErrors ( errors ) ;
72+ return ;
73+ }
74+
75+ // Check rate limiting
76+ const now = Date . now ( ) ;
77+ if ( now - lastSubmissionTime < RATE_LIMIT_MS ) {
78+ setError ( `Please wait ${ Math . ceil ( ( RATE_LIMIT_MS - ( now - lastSubmissionTime ) ) / 1000 ) } seconds before submitting again` ) ;
79+ return ;
80+ }
81+
3282 setIsSubmitting ( true ) ;
3383
34- if ( form . current ) {
35- emailjs . sendForm (
84+ try {
85+ if ( ! form . current ) throw new Error ( 'Form not found' ) ;
86+
87+ await emailjs . sendForm (
3688 emailConfig . serviceId ,
3789 emailConfig . templateId ,
3890 form . current ,
39- {
40- publicKey : emailConfig . publicKey ,
41- }
42- )
43- . then (
44- ( ) => {
45- console . log ( 'SUCCESS!' ) ;
46- setIsSubmitted ( true ) ;
47- setFormState ( {
48- name : '' ,
49- email : '' ,
50- inquiryType : 'demo' ,
51- message : ''
52- } ) ;
53- } ,
54- ( error ) => {
55- console . log ( 'FAILED...' , error . text ) ;
56- alert ( 'Failed to send message. Please try again or contact us directly.' ) ;
57- } ,
58- )
59- . finally ( ( ) => {
60- setIsSubmitting ( false ) ;
91+ emailConfig . publicKey
92+ ) ;
93+
94+ setIsSubmitted ( true ) ;
95+ setLastSubmissionTime ( Date . now ( ) ) ;
96+ setFormState ( {
97+ name : '' ,
98+ email : '' ,
99+ inquiryType : 'demo' ,
100+ message : ''
61101 } ) ;
102+ } catch ( error ) {
103+ console . error ( 'Form submission failed:' , error ) ;
104+ setError ( 'Failed to send message. Please try again or contact us directly.' ) ;
105+ } finally {
106+ setIsSubmitting ( false ) ;
62107 }
63- } , [ emailConfig ] ) ;
108+ } , [ formState , lastSubmissionTime ] ) ;
64109
65110 const handleRequestFeasibilityStudy = useCallback ( ( ) => {
66111 setFormState ( prev => ( { ...prev , inquiryType : 'integration' } ) ) ;
@@ -117,11 +162,17 @@ const ContactSection: React.FC = () => {
117162 </ button >
118163 </ div >
119164 ) : (
120- < form ref = { form } onSubmit = { handleSubmit } >
165+ < form ref = { form } onSubmit = { handleSubmit } noValidate >
121166 < h3 className = "font-display font-semibold text-xl text-white mb-6" >
122167 Contact Us
123168 </ h3 >
124169
170+ { error && (
171+ < div className = "bg-red-500/10 border border-red-500/20 rounded-lg p-4 mb-6 text-red-400" >
172+ { error }
173+ </ div >
174+ ) }
175+
125176 < div className = "space-y-4" >
126177 < div >
127178 < label htmlFor = "name" className = "block text-white/80 mb-2 text-sm" >
@@ -133,10 +184,14 @@ const ContactSection: React.FC = () => {
133184 name = "name"
134185 value = { formState . name }
135186 onChange = { handleChange }
136- required
137- className = "w-full bg-dark-700 border border-white/10 rounded-lg px-4 py-3 text-white focus:outline-none focus:ring-2 focus:ring-primary-500/50"
187+ className = { `w-full bg-dark-700 border ${
188+ formErrors . name ? 'border-red-500' : 'border-white/10'
189+ } rounded-lg px-4 py-3 text-white focus:outline-none focus:ring-2 focus:ring-primary-500/50`}
138190 placeholder = "Your name"
139191 />
192+ { formErrors . name && (
193+ < p className = "mt-1 text-sm text-red-400" > { formErrors . name } </ p >
194+ ) }
140195 </ div >
141196
142197 < div >
@@ -149,10 +204,14 @@ const ContactSection: React.FC = () => {
149204 name = "email"
150205 value = { formState . email }
151206 onChange = { handleChange }
152- required
153- className = "w-full bg-dark-700 border border-white/10 rounded-lg px-4 py-3 text-white focus:outline-none focus:ring-2 focus:ring-primary-500/50"
207+ className = { `w-full bg-dark-700 border ${
208+ formErrors . email ? 'border-red-500' : 'border-white/10'
209+ } rounded-lg px-4 py-3 text-white focus:outline-none focus:ring-2 focus:ring-primary-500/50`}
154210155211 />
212+ { formErrors . email && (
213+ < p className = "mt-1 text-sm text-red-400" > { formErrors . email } </ p >
214+ ) }
156215 </ div >
157216
158217 < div >
@@ -164,7 +223,6 @@ const ContactSection: React.FC = () => {
164223 name = "inquiryType"
165224 value = { formState . inquiryType }
166225 onChange = { handleChange }
167- required
168226 className = "w-full bg-dark-700 border border-white/10 rounded-lg px-4 py-3 text-white focus:outline-none focus:ring-2 focus:ring-primary-500/50"
169227 >
170228 < option value = "demo" > Book a Demo</ option >
@@ -184,11 +242,15 @@ const ContactSection: React.FC = () => {
184242 name = "message"
185243 value = { formState . message }
186244 onChange = { handleChange }
187- required
188245 rows = { 4 }
189- className = "w-full bg-dark-700 border border-white/10 rounded-lg px-4 py-3 text-white focus:outline-none focus:ring-2 focus:ring-primary-500/50"
246+ className = { `w-full bg-dark-700 border ${
247+ formErrors . message ? 'border-red-500' : 'border-white/10'
248+ } rounded-lg px-4 py-3 text-white focus:outline-none focus:ring-2 focus:ring-primary-500/50`}
190249 placeholder = "Tell us how we can help you..."
191- > </ textarea >
250+ />
251+ { formErrors . message && (
252+ < p className = "mt-1 text-sm text-red-400" > { formErrors . message } </ p >
253+ ) }
192254 </ div >
193255
194256 < div className = "pt-2" >
0 commit comments