This commit is contained in:
parent
00f5356db4
commit
53e2873fd4
|
|
@ -243,6 +243,7 @@ const leadSchema = z.object({
|
|||
zip: z.string().trim().max(10, 'ZIP code must be 10 characters or less').optional().or(z.literal('').transform(() => undefined)),
|
||||
message: z.string().trim().max(5000, 'Message must be 5000 characters or less').optional().or(z.literal('').transform(() => undefined)),
|
||||
service_interest: z.string().trim().max(50, 'Service interest must be 50 characters or less').optional().or(z.literal('').transform(() => undefined)),
|
||||
company_website: z.string().optional(), // Honeypot field - bots fill this, humans don't see it
|
||||
})
|
||||
|
||||
const supportSchema = z.object({
|
||||
|
|
@ -254,6 +255,7 @@ const supportSchema = z.object({
|
|||
priority: z.enum(['low', 'medium', 'high'], {
|
||||
errorMap: () => ({ message: 'Priority must be low, medium, or high' }),
|
||||
}).transform((val) => val?.toLowerCase() ?? undefined).optional().or(z.literal('').transform(() => undefined)),
|
||||
company_website: z.string().optional(), // Honeypot field - bots fill this, humans don't see it
|
||||
})
|
||||
|
||||
// --- Zoho CRM Forwarding (best-effort, fire-and-forget) ---
|
||||
|
|
@ -532,6 +534,13 @@ app.get('/api/health', (req, res) => {
|
|||
|
||||
// Submit lead
|
||||
app.post('/api/leads', express.json({ limit: '1mb' }), (req, res) => {
|
||||
// Honeypot check - if filled, it's a bot
|
||||
if (req.body.company_website) {
|
||||
log.info('[Spam] Honeypot triggered, ignoring submission')
|
||||
// Return success to bot so it doesn't retry
|
||||
return res.json({ success: true, message: "Thanks! We'll be in touch shortly." })
|
||||
}
|
||||
|
||||
let sanitized
|
||||
try {
|
||||
const parsed = leadSchema.safeParse(req.body)
|
||||
|
|
@ -603,6 +612,13 @@ app.post('/api/leads', express.json({ limit: '1mb' }), (req, res) => {
|
|||
|
||||
// Submit support request
|
||||
app.post('/api/support', express.json({ limit: '1mb' }), (req, res) => {
|
||||
// Honeypot check - if filled, it's a bot
|
||||
if (req.body.company_website) {
|
||||
log.info('[Spam] Honeypot triggered, ignoring submission')
|
||||
// Return success to bot so it doesn't retry
|
||||
return res.json({ success: true, message: "Thanks! We'll get back to you soon." })
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = supportSchema.safeParse(req.body)
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ const Contact = () => {
|
|||
zip: '',
|
||||
message: '',
|
||||
service_interest: '',
|
||||
company_website: '', // Honeypot field - hidden from humans, bots will fill it
|
||||
})
|
||||
const [errors, setErrors] = useState({
|
||||
company: '',
|
||||
|
|
@ -50,7 +51,12 @@ const Contact = () => {
|
|||
})
|
||||
},
|
||||
onError: (error) => {
|
||||
// 409 means duplicate email - this is actually good news
|
||||
if (error.response?.status === 409) {
|
||||
toast.success("We already have your submission! We'll be in touch.")
|
||||
} else {
|
||||
toast.error(error.message || 'Failed to submit form. Please try again.')
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
|
|
@ -326,6 +332,17 @@ const Contact = () => {
|
|||
)}
|
||||
</div>
|
||||
|
||||
<div className="absolute opacity-0 h-0 overflow-hidden" aria-hidden="true">
|
||||
<input
|
||||
type="text"
|
||||
name="company_website"
|
||||
tabIndex="-1"
|
||||
autoComplete="off"
|
||||
value={formState.company_website}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full"
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ const Support = () => {
|
|||
phone: '',
|
||||
issue: '',
|
||||
priority: 'medium',
|
||||
company_website: '', // Honeypot field - hidden from humans, bots will fill it
|
||||
})
|
||||
const [errors, setErrors] = useState({
|
||||
name: '',
|
||||
|
|
@ -338,6 +339,17 @@ const Support = () => {
|
|||
)}
|
||||
</div>
|
||||
|
||||
<div className="absolute opacity-0 h-0 overflow-hidden" aria-hidden="true">
|
||||
<input
|
||||
type="text"
|
||||
name="company_website"
|
||||
tabIndex="-1"
|
||||
autoComplete="off"
|
||||
value={formState.company_website}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full"
|
||||
|
|
|
|||
Loading…
Reference in New Issue