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 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-18 12:11:56 -05:00
company _website : '' ,
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
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 = ( ) => {
2026-05-18 12:11:56 -05:00
const newErrors = { company : '' , name : '' , email : '' , message : '' }
2026-05-13 18:10:04 -05:00
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'
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 )
2026-05-18 12:11:56 -05:00
toast . success ( "Thanks! We'll be in touch shortly." )
setFormState ( { company : '' , name : '' , email : '' , phone : '' , zip : '' , message : '' , service _interest : '' } )
setErrors ( { company : '' , name : '' , email : '' , message : '' } )
2026-05-17 22:33:11 -05:00
} catch ( error ) {
if ( error . response ? . status === 409 ) {
2026-05-18 12:11:56 -05:00
toast . success ( "We already have your submission! We'll be in touch." )
2026-05-17 22:33:11 -05:00
} 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-18 12:11:56 -05:00
if ( errors [ name ] ) setErrors ( prev => ( { ... prev , [ name ] : '' } ) )
2026-05-12 01:04:17 -05:00
}
2026-05-18 12:11:56 -05:00
const contactDetails = [
{
label : 'Phone' ,
icon : (
< svg className = "h-5 w-5 text-primary-cyan" fill = "none" viewBox = "0 0 24 24" stroke = "currentColor" strokeWidth = { 1.5 } >
< path strokeLinecap = "round" strokeLinejoin = "round" d = "M2.25 6.75c0 8.284 6.716 15 15 15h2.25a2.25 2.25 0 002.25-2.25v-1.372c0-.516-.351-.966-.852-1.091l-4.423-1.106c-.44-.11-.902.055-1.173.417l-.97 1.293c-.282.376-.769.542-1.21.38a12.035 12.035 0 01-7.143-7.143c-.162-.441.004-.928.38-1.21l1.293-.97c.363-.271.527-.734.417-1.173L6.963 3.102a1.125 1.125 0 00-1.091-.852H4.5A2.25 2.25 0 002.25 4.5v2.25z" / >
< / svg >
) ,
content : (
< div >
< a href = "tel:+13217308020" className = "block text-white hover:text-primary-cyan transition-colors" > ( 321 ) 730 - 8020 < / a >
< a href = "tel:+18886562850" className = "block text-white/70 text-sm hover:text-primary-cyan transition-colors mt-0.5" > ( 888 ) 656 - 2850 Toll - Free < / a >
< / div >
) ,
} ,
{
label : 'Office' ,
icon : (
< svg className = "h-5 w-5 text-primary-cyan" fill = "none" viewBox = "0 0 24 24" stroke = "currentColor" strokeWidth = { 1.5 } >
< path strokeLinecap = "round" strokeLinejoin = "round" d = "M15 10.5a3 3 0 11-6 0 3 3 0 016 0z" / >
< path strokeLinecap = "round" strokeLinejoin = "round" d = "M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1115 0z" / >
< / svg >
) ,
content : (
< a
href = "https://maps.google.com/?q=7901+4th+St+N+St+Petersburg+FL+33702"
target = "_blank"
rel = "noopener noreferrer"
className = "text-white hover:text-primary-cyan transition-colors leading-relaxed"
>
< span className = "block" > 7901 4 th St N < / span >
< span className = "block" > St . Petersburg , FL 33702 < / span >
< / a >
) ,
} ,
{
label : 'Hours' ,
icon : (
< svg className = "h-5 w-5 text-primary-cyan" fill = "none" viewBox = "0 0 24 24" stroke = "currentColor" strokeWidth = { 1.5 } >
< path strokeLinecap = "round" strokeLinejoin = "round" d = "M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" / >
< / svg >
) ,
content : < p className = "text-white/80 text-sm" > Mon – Fri : 8 : 00 AM – 6 : 00 PM CT < / p > ,
} ,
]
const trustPoints = [
'8x8 Certified Partner with proven expertise' ,
'Veteran-owned — 25+ years of experience' ,
'SMB to Enterprise solutions' ,
'No vendor bias — we recommend what fits' ,
]
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-18 09:35:29 -05:00
2026-05-18 12:11:56 -05:00
{ /* Hero */ }
< section className = "bg-primary-navy py-16 lg:py-20" >
< div className = "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8" >
< h1 className = "text-4xl md:text-5xl font-bold text-white mb-4" > Let ' s Talk < / h1 >
< p className = "text-xl text-white/70 max-w-2xl" >
Tell us about your business and we ' ll cut through the noise to find what actually works for you .
< / p >
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
2026-05-18 12:11:56 -05:00
{ /* Contact Body */ }
< section className = "bg-background py-16 lg:py-20" >
2026-05-17 17:11:29 -05:00
< div className = "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8" >
2026-05-18 12:11:56 -05:00
< div className = "grid grid-cols-1 lg:grid-cols-5 rounded-2xl overflow-hidden shadow-xl border border-border" >
{ /* Left: Info panel */ }
< div className = "lg:col-span-2 bg-primary-navy text-white p-8 lg:p-10 flex flex-col gap-10" >
< div className = "space-y-7" >
{ contactDetails . map ( ( item ) => (
< div key = { item . label } className = "flex items-start gap-4" >
< div className = "w-10 h-10 bg-white/10 rounded-lg flex items-center justify-center flex-shrink-0" >
{ item . icon }
< / div >
< div >
< p className = "text-white/50 text-xs uppercase tracking-wider mb-1" > { item . label } < / p >
{ item . content }
< / div >
2026-05-17 17:11:29 -05:00
< / div >
2026-05-18 12:11:56 -05:00
) ) }
2026-05-17 17:11:29 -05:00
< / div >
2026-05-12 01:04:17 -05:00
2026-05-18 12:11:56 -05:00
< div className = "border-t border-white/10" / >
< div >
< p className = "text-white/50 text-xs uppercase tracking-wider mb-5" > Why Queue North < / p >
< ul className = "space-y-4" >
{ trustPoints . map ( ( point , i ) => (
< li key = { i } className = "flex items-start gap-3 text-white/80 text-sm leading-relaxed" >
< svg className = "h-4 w-4 text-primary-cyan flex-shrink-0 mt-0.5" fill = "none" viewBox = "0 0 24 24" stroke = "currentColor" strokeWidth = { 2.5 } >
< path strokeLinecap = "round" strokeLinejoin = "round" d = "M4.5 12.75l6 6 9-13.5" / >
2026-05-17 17:11:29 -05:00
< / svg >
2026-05-18 12:11:56 -05:00
{ point }
2026-05-17 17:11:29 -05:00
< / li >
) ) }
< / ul >
< / div >
2026-05-12 01:04:17 -05:00
< / div >
2026-05-18 12:11:56 -05:00
{ /* Right: Form panel */ }
< div className = "lg:col-span-3 bg-white p-8 lg:p-10" >
< h2 className = "text-2xl font-bold text-primary-navy mb-1" > Send Us a Message < / h2 >
< p className = "text-soft-text text-sm mb-8" > We typically respond within one business day . < / p >
< form onSubmit = { handleSubmit } noValidate className = { ` space-y-5 ${ isSubmitting ? 'opacity-70 pointer-events-none' : '' } ` } >
{ /* Company */ }
2026-05-17 17:11:29 -05:00
< div >
2026-05-18 12:11:56 -05:00
< label htmlFor = "company" className = "block text-sm font-medium text-text mb-1.5" >
Company Name < span className = "text-red-500" > * < / span >
2026-05-17 17:11:29 -05:00
< / label >
< Input
type = "text"
id = "company"
name = "company"
value = { formState . company }
onChange = { handleChange }
required
placeholder = "Your company name"
2026-05-18 12:11:56 -05:00
className = { debouncedErrors . company ? 'border-red-500 focus-visible:ring-red-500' : '' }
2026-05-17 17:11:29 -05:00
/ >
2026-05-18 12:11:56 -05:00
{ debouncedErrors . company && < p className = "text-xs text-red-500 mt-1" > { debouncedErrors . company } < / p > }
2026-05-17 17:11:29 -05:00
< / div >
2026-05-12 01:04:17 -05:00
2026-05-18 12:11:56 -05:00
{ /* Name + Email */ }
< div className = "grid grid-cols-1 sm:grid-cols-2 gap-4" >
< div >
< label htmlFor = "name" className = "block text-sm font-medium text-text mb-1.5" >
Name < span className = "text-red-500" > * < / span >
< / label >
< Input
type = "text"
id = "name"
name = "name"
value = { formState . name }
onChange = { handleChange }
required
placeholder = "Your full name"
className = { debouncedErrors . name ? 'border-red-500 focus-visible:ring-red-500' : '' }
/ >
{ debouncedErrors . name && < p className = "text-xs text-red-500 mt-1" > { debouncedErrors . name } < / p > }
< / div >
< div >
< label htmlFor = "email" className = "block text-sm font-medium text-text mb-1.5" >
Email < span className = "text-red-500" > * < / span >
< / label >
< Input
type = "email"
id = "email"
name = "email"
value = { formState . email }
onChange = { handleChange }
required
placeholder = "you@company.com"
className = { debouncedErrors . email ? 'border-red-500 focus-visible:ring-red-500' : '' }
/ >
{ debouncedErrors . email && < p className = "text-xs text-red-500 mt-1" > { debouncedErrors . email } < / p > }
< / div >
2026-05-17 17:11:29 -05:00
< / div >
2026-05-18 12:11:56 -05:00
{ /* Phone + Service Interest */ }
< div className = "grid grid-cols-1 sm:grid-cols-2 gap-4" >
< div >
< label htmlFor = "phone" className = "block text-sm font-medium text-text mb-1.5" >
Phone < span className = "text-soft-text font-normal" > ( optional ) < / span >
< / label >
< Input
type = "tel"
id = "phone"
name = "phone"
value = { formState . phone }
onChange = { handleChange }
placeholder = "(555) 123-4567"
/ >
< / div >
< div >
< label htmlFor = "service_interest" className = "block text-sm font-medium text-text mb-1.5" >
Service Interest < span className = "text-soft-text font-normal" > ( optional ) < / span >
< / 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 >
2026-05-17 17:11:29 -05:00
< / div >
2026-05-18 12:11:56 -05:00
{ /* Message */ }
2026-05-17 17:11:29 -05:00
< div >
2026-05-18 12:11:56 -05:00
< label htmlFor = "message" className = "block text-sm font-medium text-text mb-1.5" >
Message < span className = "text-red-500" > * < / span >
2026-05-17 17:11:29 -05:00
< / label >
< Textarea
id = "message"
name = "message"
value = { formState . message }
onChange = { handleChange }
required
placeholder = "Tell us about your needs..."
2026-05-18 12:11:56 -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 ${ debouncedErrors . message ? 'border-red-500 focus-visible:ring-red-500' : '' } ` }
2026-05-17 17:11:29 -05:00
rows = { 5 }
/ >
2026-05-18 12:11:56 -05:00
{ debouncedErrors . message && < p className = "text-xs text-red-500 mt-1" > { debouncedErrors . message } < / p > }
2026-05-17 17:11:29 -05:00
< / div >
2026-05-12 01:04:17 -05:00
2026-05-18 12:11:56 -05:00
{ /* Honeypot */ }
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-18 12:11:56 -05:00
< Button type = "submit" className = "w-full h-11" disabled = { isSubmitting } >
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 >
2026-05-18 12:11:56 -05:00
Sending ...
2026-05-17 22:35:55 -05:00
< / >
) : (
2026-05-18 12:11:56 -05:00
'Send Message'
2026-05-17 22:35:55 -05:00
) }
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