2026-05-17 20:03:42 -05:00
import { Helmet } from 'react-helmet-async'
2026-05-12 01:04:17 -05:00
import { useState } from 'react'
2026-05-13 22:07:35 -05:00
import { toast } from 'sonner'
2026-05-12 01:04:17 -05:00
import { Button } from '@/components/ui/Button'
import { Input } from '@/components/ui/Input'
import { Textarea } from '@/components/ui/Textarea'
import { Select } from '@/components/ui/Select'
2026-05-17 18:03:55 -05:00
import { Link } from 'react-router-dom'
2026-05-17 22:33:11 -05:00
import { submitLead } from '@/lib/api'
2026-05-17 16:10:10 -05:00
import { useDebounce } from '@/hooks/useDebounce'
2026-05-12 01:04:17 -05:00
const Contact = ( ) => {
const [ formState , setFormState ] = useState ( {
company : '' ,
name : '' ,
email : '' ,
phone : '' ,
zip : '' ,
message : '' ,
service _interest : '' ,
2026-05-17 21:51:53 -05:00
company _website : '' , // Honeypot field - hidden from humans, bots will fill it
2026-05-12 01:04:17 -05:00
} )
2026-05-13 18:10:04 -05:00
const [ errors , setErrors ] = useState ( {
company : '' ,
name : '' ,
email : '' ,
message : '' ,
} )
2026-05-17 16:10:10 -05:00
// Debounce validation errors so they don't flash on every keystroke
const debouncedErrors = useDebounce ( errors , 300 )
2026-05-17 22:33:11 -05:00
const [ isSubmitting , setIsSubmitting ] = useState ( false )
2026-05-12 01:04:17 -05:00
2026-05-13 18:10:04 -05:00
const validateForm = ( ) => {
const newErrors = {
company : '' ,
name : '' ,
email : '' ,
message : '' ,
}
// Validate required fields
if ( ! formState . company . trim ( ) ) newErrors . company = 'Company name is required'
if ( ! formState . name . trim ( ) ) newErrors . name = 'Name is required'
if ( ! formState . message . trim ( ) ) newErrors . message = 'Message is required'
// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if ( ! formState . email . trim ( ) ) {
newErrors . email = 'Email is required'
} else if ( ! emailRegex . test ( formState . email ) ) {
newErrors . email = 'Please enter a valid email address'
}
const hasErrors = Object . values ( newErrors ) . some ( error => error !== '' )
setErrors ( newErrors )
if ( hasErrors ) {
toast . error ( 'Please fix the errors in the form' )
return false
}
return true
}
2026-05-12 01:04:17 -05:00
const handleSubmit = ( e ) => {
e . preventDefault ( )
2026-05-13 18:10:04 -05:00
if ( ! validateForm ( ) ) return
2026-05-17 22:33:11 -05:00
handleSubmitForm ( )
}
const handleSubmitForm = async ( ) => {
setIsSubmitting ( true )
try {
await submitLead ( formState )
toast . success ( "Thanks! We\'ll be in touch shortly." )
setFormState ( {
company : '' ,
name : '' ,
email : '' ,
phone : '' ,
zip : '' ,
message : '' ,
service _interest : '' ,
} )
setErrors ( {
company : '' ,
name : '' ,
email : '' ,
message : '' ,
} )
} catch ( 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.' )
}
} finally {
setIsSubmitting ( false )
}
2026-05-12 01:04:17 -05:00
}
const handleChange = ( e ) => {
const { name , value } = e . target
setFormState ( prev => ( { ... prev , [ name ] : value } ) )
2026-05-13 18:10:04 -05:00
// Clear error for this field as user types
if ( errors [ name ] ) {
setErrors ( prev => ( { ... prev , [ name ] : '' } ) )
}
2026-05-12 01:04:17 -05:00
}
return (
2026-05-17 17:11:29 -05:00
< >
2026-05-17 20:03:42 -05:00
< Helmet >
< title > Contact Queue North | Schedule a Consultation < / title >
2026-05-17 20:44:18 -05:00
< meta name = "description" content = "Contact Queue North Technologies to schedule a free consultation. Call (321) 730-8020 or toll-free (888) 656-2850 or fill out our form for business phone, UCaaS, IT support, and networking solutions." / >
2026-05-17 20:03:42 -05:00
< meta property = "og:title" content = "Contact Queue North | Schedule a Consultation" / >
2026-05-17 20:44:18 -05:00
< meta property = "og:description" content = "Schedule a free consultation with Queue North Technologies. Business communications and IT solutions." / >
2026-05-17 20:03:42 -05:00
< meta property = "og:url" content = "https://queuenorth.com/contact" / >
< meta property = "og:type" content = "website" / >
2026-05-17 22:01:27 -05:00
< meta property = "og:image" content = "https://queuenorth.com/assets/og-image.png" / >
2026-05-17 20:03:42 -05:00
< meta property = "og:site_name" content = "Queue North Technologies" / >
< / Helmet >
2026-05-12 01:04:17 -05:00
{ /* Page Hero */ }
2026-05-17 17:11:29 -05:00
< section className = "bg-background py-16 lg:py-24" >
< div className = "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8" >
< div >
< h1 className = "text-4xl md:text-5xl font-bold text-primary-navy mb-6" > Contact Us < / h1 >
< p className = "text-xl text-soft-text max-w-3xl mb-8" >
Have questions about our services ? We 're here to help. Fill out the form and we' ll get back to you shortly .
2026-05-12 01:04:17 -05:00
< / p >
2026-05-17 17:11:29 -05:00
< div className = "flex flex-wrap gap-4 mb-8" >
< div className = "flex items-center gap-2 px-4 py-2 bg-primary-navy/10 rounded-lg text-primary-navy" >
< svg className = "h-5 w-5" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = "2" d = "M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 5.13a1 1 0 01-1.31.42a2 2 0 00-2.63-.54l-2.238 5.03a2 2 0 01-2.71.319L2.663 12H4a2 2 0 012 2v2a2 2 0 01-2 2H2a2 2 0 01-2-2V5z" / >
< / svg >
2026-05-17 22:47:03 -05:00
< a href = "tel:+13217308020" className = "text-sm font-medium hover:underline" aria - label = "Call (321) 730-8020" > ( 321 ) 730 - 8020 < / a >
2026-05-17 20:44:18 -05:00
< / div >
< div className = "flex items-center gap-2 px-4 py-2 bg-primary-navy/10 rounded-lg text-primary-navy" >
< svg className = "h-5 w-5" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = "2" d = "M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 5.13a1 1 0 01-1.31.42a2 2 0 00-2.63-.54l-2.238 5.03a2 2 0 01-2.71.319L2.663 12H4a2 2 0 012 2v2a2 2 0 01-2 2H2a2 2 0 01-2-2V5z" / >
< / svg >
2026-05-17 22:47:03 -05:00
< a href = "tel:+18886562850" className = "text-sm font-medium hover:underline" aria - label = "Call (888) 656-2850" > ( 888 ) 656 - 2850 < / a >
2026-05-17 14:35:29 -05:00
< / div >
2026-05-17 17:11:29 -05:00
< div className = "flex items-center gap-2 px-4 py-2 bg-primary-navy/10 rounded-lg text-primary-navy" >
< svg className = "h-5 w-5" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = "2" d = "M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" / >
< / svg >
2026-05-17 22:47:03 -05:00
< a href = "mailto:info@queuenorth.com" className = "text-sm font-medium hover:underline" aria - label = "Send email to info@queuenorth.com" > info @ queuenorth . com < / a >
2026-05-12 01:04:17 -05:00
< / div >
< / div >
2026-05-17 17:11:29 -05:00
< div className = "mb-8" >
2026-05-17 21:44:48 -05:00
< a href = "#contact-form" className = "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-6 bg-primary-cyan text-primary-navy hover:bg-cyan-600 transition-colors" >
Fill Out the Form
< / a >
2026-05-17 17:11:29 -05:00
< / div >
2026-05-12 01:04:17 -05:00
< / div >
< / div >
2026-05-17 17:11:29 -05:00
< / section >
2026-05-12 01:04:17 -05:00
2026-05-17 17:11:29 -05:00
{ /* Contact Form */ }
2026-05-17 21:44:48 -05:00
< section id = "contact-form" className = "bg-background py-16" >
2026-05-17 17:11:29 -05:00
< div className = "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8" >
< div className = "grid grid-cols-1 lg:grid-cols-2 gap-12" >
{ /* Left - Info */ }
2026-05-12 01:04:17 -05:00
< div >
2026-05-17 17:11:29 -05:00
< div className = "mb-8" >
< h2 className = "text-2xl font-bold text-primary-navy mb-4" > Get in Touch < / h2 >
< p className = "text-soft-text mb-6" >
Our team of communications and infrastructure experts is ready to help you find the right solution for your business needs .
< / p >
< div className = "space-y-4" >
< div >
< h3 className = "font-semibold text-text mb-2" > Hours of Operation < / h3 >
< p className = "text-soft-text" > Monday - Friday : 8 : 00 AM - 6 : 00 PM CT < / p >
< / div >
< div >
< h3 className = "font-semibold text-text mb-2" > Phone < / h3 >
2026-05-17 22:47:03 -05:00
< p className = "text-soft-text" > < a href = "tel:+13217308020" className = "hover:underline" aria - label = "Call (321) 730-8020" > ( 321 ) 730 - 8020 < / a > < / p >
< p className = "text-soft-text" > < a href = "tel:+18886562850" className = "hover:underline" aria - label = "Call (888) 656-2850 - Toll Free" > ( 888 ) 656 - 2850 - Toll Free < / a > < / p >
2026-05-17 17:11:29 -05:00
< / div >
< div >
< h3 className = "font-semibold text-text mb-2" > Contact Us < / h3 >
< p className = "text-soft-text" > Use the form on the right to get in touch with our team . < / p >
< / div >
< / div >
< / div >
2026-05-12 01:04:17 -05:00
2026-05-17 17:11:29 -05:00
< div className = "bg-section-alt rounded-lg p-6" >
< h3 className = "font-semibold text-primary-navy mb-4" > Why Choose Queue North ? < / h3 >
< ul className = "space-y-3" >
{ [
'8x8 Certified Partner with proven expertise' ,
'25+ years of industry experience' ,
'SMB to Enterprise solutions' ,
'Focus on your business outcomes' ,
] . map ( ( item , index ) => (
< li key = { index } className = "flex items-center gap-3 text-text" >
< svg className = "h-5 w-5 text-primary-navy" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
< path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = "2" d = "M5 13l4 4L19 7" / >
< / svg >
{ item }
< / li >
) ) }
< / ul >
< / div >
2026-05-12 01:04:17 -05:00
< / div >
2026-05-17 17:11:29 -05:00
{ /* Right - Form */ }
2026-05-12 01:04:17 -05:00
< div >
2026-05-17 22:33:11 -05:00
< form onSubmit = { handleSubmit } noValidate className = { ` space-y-6 ${ isSubmitting ? 'opacity-70 pointer-events-none' : '' } ` } >
2026-05-17 17:11:29 -05:00
< div >
< label htmlFor = "company" className = "block text-sm font-medium text-text mb-2" >
Company Name < span className = "text-red-600" > * < / span >
< / label >
< Input
type = "text"
id = "company"
name = "company"
value = { formState . company }
onChange = { handleChange }
required
placeholder = "Your company name"
className = { errors . company ? 'border-red-500 focus-visible:ring-red-500' : '' }
/ >
{ debouncedErrors . company && (
< p className = "text-xs text-red-600 mt-1" > { debouncedErrors . company } < / p >
) }
< / div >
2026-05-12 01:04:17 -05:00
2026-05-17 17:11:29 -05:00
< div >
< label htmlFor = "name" className = "block text-sm font-medium text-text mb-2" >
Name < span className = "text-red-600" > * < / span >
< / label >
< Input
type = "text"
id = "name"
name = "name"
value = { formState . name }
onChange = { handleChange }
required
placeholder = "Your full name"
className = { errors . name ? 'border-red-500 focus-visible:ring-red-500' : '' }
/ >
{ debouncedErrors . name && (
< p className = "text-xs text-red-600 mt-1" > { debouncedErrors . name } < / p >
) }
< / div >
2026-05-12 01:04:17 -05:00
2026-05-17 17:11:29 -05:00
< div >
< label htmlFor = "email" className = "block text-sm font-medium text-text mb-2" >
Email < span className = "text-red-600" > * < / span >
< / label >
< Input
type = "email"
id = "email"
name = "email"
value = { formState . email }
onChange = { handleChange }
required
placeholder = "your.email@example.com"
className = { errors . email ? 'border-red-500 focus-visible:ring-red-500' : '' }
/ >
{ debouncedErrors . email && (
< p className = "text-xs text-red-600 mt-1" > { debouncedErrors . email } < / p >
) }
< / div >
2026-05-12 01:04:17 -05:00
2026-05-17 17:11:29 -05:00
< div >
< label htmlFor = "phone" className = "block text-sm font-medium text-text mb-2" >
Phone ( Optional )
< / label >
< Input
type = "tel"
id = "phone"
name = "phone"
value = { formState . phone }
onChange = { handleChange }
placeholder = "(555) 123-4567"
/ >
< / div >
2026-05-12 01:04:17 -05:00
2026-05-17 17:11:29 -05:00
< div >
< label htmlFor = "zip" className = "block text-sm font-medium text-text mb-2" >
ZIP Code ( Optional )
< / label >
< Input
type = "text"
id = "zip"
name = "zip"
value = { formState . zip }
onChange = { handleChange }
placeholder = "12345"
/ >
< / div >
< div >
< label htmlFor = "service_interest" className = "block text-sm font-medium text-text mb-2" >
Service Interest ( Optional )
< / label >
< Select
id = "service_interest"
name = "service_interest"
value = { formState . service _interest }
onChange = { handleChange }
>
< option value = "" > Select a service ... < / option >
< option value = "unified-communications" > Unified Communications < / option >
< option value = "contact-center" > Contact Center < / option >
< option value = "managed-support" > Managed Support < / option >
< option value = "consulting-training" > Consulting & Training < / option >
< option value = "infrastructure-cabling" > Infrastructure Cabling < / option >
< option value = "wireless-access" > Wireless Access < / option >
< option value = "local-networking" > Local Networking < / option >
< / Select >
< / div >
< div >
< label htmlFor = "message" className = "block text-sm font-medium text-text mb-2" >
Message < span className = "text-red-600" > * < / span >
< / label >
< Textarea
id = "message"
name = "message"
value = { formState . message }
onChange = { handleChange }
required
placeholder = "Tell us about your needs..."
2026-05-17 17:42:03 -05:00
className = { ` w-full rounded-md border bg-background px-3 py-2 text-sm ring-offset-[#F8FAFC] placeholder:text-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-navy focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ${ errors . message ? 'border-red-500 focus-visible:ring-red-500' : '' } ` }
2026-05-17 17:11:29 -05:00
rows = { 5 }
/ >
{ debouncedErrors . message && (
< p className = "text-xs text-red-600 mt-1" > { debouncedErrors . message } < / p >
) }
< / div >
2026-05-12 01:04:17 -05:00
2026-05-17 21:51:53 -05:00
< 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 >
2026-05-17 17:11:29 -05:00
< Button
type = "submit"
className = "w-full"
2026-05-17 22:33:11 -05:00
disabled = { isSubmitting }
2026-05-17 17:11:29 -05:00
>
2026-05-17 22:35:55 -05:00
{ isSubmitting ? (
< >
< svg className = "animate-spin -ml-1 mr-2 h-4 w-4" xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 24 24" >
< circle className = "opacity-25" cx = "12" cy = "12" r = "10" stroke = "currentColor" strokeWidth = "4" / >
< path className = "opacity-75" fill = "currentColor" d = "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" / >
< / svg >
Submitting ...
< / >
) : (
'Request Consultation'
) }
2026-05-17 17:11:29 -05:00
< / Button >
< / form >
< / div >
< / div >
2026-05-12 01:04:17 -05:00
< / div >
2026-05-17 17:11:29 -05:00
< / section >
< / >
2026-05-12 01:04:17 -05:00
)
}
export default Contact