@@ -28,6 +28,7 @@ type ValueType = {
2828 email : string ;
2929 company : string ;
3030 message ?: string ;
31+ website ?: string ; // Honeypot field
3132} ;
3233
3334const validationSchema = yup . object ( ) . shape ( {
@@ -40,6 +41,7 @@ const validationSchema = yup.object().shape({
4041 . required ( 'Work email is a required field' ) ,
4142 company : yup . string ( ) . trim ( ) . required ( 'Company name is a required field' ) ,
4243 message : yup . string ( ) . trim ( ) . optional ( ) ,
44+ website : yup . string ( ) . trim ( ) . optional ( ) , // Honeypot field - should be empty
4345} ) ;
4446
4547const getButtonTitle = ( formId : string ) => {
@@ -131,7 +133,15 @@ const detectSpamSubmission = (values: ValueType): boolean => {
131133 if ( company . trim ( ) . length <= 2 ) spamScore += 3 ;
132134
133135 // Medium confidence indicators (2 points each)
134- const freeEmailDomains = [ 'gmail.com' , 'yahoo.com' , 'hotmail.com' , 'outlook.com' , 'aol.com' , 'icloud.com' , 't-online.de' ] ;
136+ const freeEmailDomains = [
137+ 'gmail.com' ,
138+ 'yahoo.com' ,
139+ 'hotmail.com' ,
140+ 'outlook.com' ,
141+ 'aol.com' ,
142+ 'icloud.com' ,
143+ 't-online.de' ,
144+ ] ;
135145 const emailDomain = email . toLowerCase ( ) . split ( '@' ) [ 1 ] || '' ;
136146 if ( ! emailDomain ) {
137147 // Invalid email format (missing domain) - likely spam
@@ -141,7 +151,10 @@ const detectSpamSubmission = (values: ValueType): boolean => {
141151 }
142152
143153 // Low confidence indicators (1 point each)
144- const messageWords = ( message || '' ) . trim ( ) . split ( / \s + / ) . filter ( word => word . length > 0 ) ;
154+ const messageWords = ( message || '' )
155+ . trim ( )
156+ . split ( / \s + / )
157+ . filter ( ( word ) => word . length > 0 ) ;
145158 if ( messageWords . length < 5 ) spamScore += 1 ;
146159
147160 // Flag as spam if score >= 5
@@ -176,7 +189,18 @@ const ContactForm = ({
176189 } = useForm < ValueType > ( { resolver : yupResolver ( validationSchema ) } ) ;
177190
178191 const onSubmit = async ( values : ValueType ) => {
179- const { firstname, lastname, email, company, message } = values ;
192+ const { firstname, lastname, email, company, message, website } = values ;
193+
194+ // Honeypot check - if filled, it's a bot
195+ if ( website ?. trim ( ) ) {
196+ // Silently reject the submission (don't show error to bot)
197+ setButtonState ( STATES . SUCCESS ) ;
198+ setTimeout ( ( ) => {
199+ setButtonState ( STATES . DEFAULT ) ;
200+ reset ( ) ;
201+ } , BUTTON_SUCCESS_TIMEOUT_MS ) ;
202+ return ;
203+ }
180204
181205 setButtonState ( STATES . LOADING ) ;
182206 setFormError ( '' ) ;
@@ -313,6 +337,20 @@ const ContactForm = ({
313337 error = { errors ?. message ?. message }
314338 { ...register ( 'message' ) }
315339 />
340+ { /* Honeypot field - hidden from users but visible to bots */ }
341+ < div className = "pointer-events-none absolute left-[-9999px] opacity-0" aria-hidden = "true" >
342+ < label htmlFor = "website-field" tabIndex = { - 1 } >
343+ Website
344+ </ label >
345+ < Field
346+ id = "website-field"
347+ type = "text"
348+ placeholder = "https://yourcompany.com"
349+ autoComplete = "off"
350+ tabIndex = { - 1 }
351+ { ...register ( 'website' ) }
352+ />
353+ </ div >
316354 < div className = "relative col-span-full flex items-center gap-x-5 sm:flex-col sm:items-start sm:gap-y-2" >
317355 < div className = "flex w-full max-w-[260px] flex-col items-center lg:max-w-[320px] sm:max-w-full" >
318356 < Button
0 commit comments