2026-05-12 01:04:17 -05:00
|
|
|
import { useState } from 'react'
|
|
|
|
|
import { useMutation } from '@tanstack/react-query'
|
|
|
|
|
import { useToast } from 'sonner'
|
|
|
|
|
import { Button } from '@/components/ui/Button'
|
|
|
|
|
import { Input } from '@/components/ui/Input'
|
|
|
|
|
import { Textarea } from '@/components/ui/Textarea'
|
|
|
|
|
import { Select } from '@/components/ui/Select'
|
|
|
|
|
import { api } from '@/lib/api'
|
|
|
|
|
|
|
|
|
|
const Contact = () => {
|
|
|
|
|
const { toast } = useToast()
|
|
|
|
|
const [formState, setFormState] = useState({
|
|
|
|
|
company: '',
|
|
|
|
|
name: '',
|
|
|
|
|
email: '',
|
|
|
|
|
phone: '',
|
|
|
|
|
zip: '',
|
|
|
|
|
message: '',
|
|
|
|
|
service_interest: '',
|
|
|
|
|
})
|
2026-05-13 18:10:04 -05:00
|
|
|
const [errors, setErrors] = useState({
|
|
|
|
|
company: '',
|
|
|
|
|
name: '',
|
|
|
|
|
email: '',
|
|
|
|
|
message: '',
|
|
|
|
|
})
|
2026-05-12 01:04:17 -05:00
|
|
|
|
|
|
|
|
const mutation = useMutation({
|
|
|
|
|
mutationFn: (data) => api.post('/leads', data),
|
|
|
|
|
onSuccess: () => {
|
|
|
|
|
toast.success('Thanks! We\'ll be in touch shortly.')
|
|
|
|
|
setFormState({
|
|
|
|
|
company: '',
|
|
|
|
|
name: '',
|
|
|
|
|
email: '',
|
|
|
|
|
phone: '',
|
|
|
|
|
zip: '',
|
|
|
|
|
message: '',
|
|
|
|
|
service_interest: '',
|
|
|
|
|
})
|
2026-05-13 18:10:04 -05:00
|
|
|
setErrors({
|
|
|
|
|
company: '',
|
|
|
|
|
name: '',
|
|
|
|
|
email: '',
|
|
|
|
|
message: '',
|
|
|
|
|
})
|
2026-05-12 01:04:17 -05:00
|
|
|
},
|
|
|
|
|
onError: (error) => {
|
|
|
|
|
toast.error(error.message || 'Failed to submit form. Please try again.')
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
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-12 01:04:17 -05:00
|
|
|
mutation.mutate(formState)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 (
|
|
|
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16 lg:py-24">
|
|
|
|
|
{/* Page Hero */}
|
|
|
|
|
<section className="mb-16">
|
|
|
|
|
<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">
|
|
|
|
|
Have questions about our services? We're here to help. Fill out the form and we'll get back to you shortly.
|
|
|
|
|
</p>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
{/* Contact Form */}
|
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
|
|
|
|
|
{/* Left - Info */}
|
|
|
|
|
<div>
|
|
|
|
|
<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">Email</h3>
|
|
|
|
|
<p className="text-soft-text">info@queuenorth.com</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Right - Form */}
|
|
|
|
|
<div>
|
2026-05-13 18:10:04 -05:00
|
|
|
<form onSubmit={handleSubmit} className={`space-y-6 ${mutation.isPending ? 'opacity-70 pointer-events-none' : ''}`}>
|
2026-05-12 01:04:17 -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"
|
2026-05-13 18:10:04 -05:00
|
|
|
className={errors.company ? 'border-red-500 focus-visible:ring-red-500' : ''}
|
2026-05-12 01:04:17 -05:00
|
|
|
/>
|
2026-05-13 18:10:04 -05:00
|
|
|
{errors.company && (
|
|
|
|
|
<p className="text-xs text-red-600 mt-1">{errors.company}</p>
|
|
|
|
|
)}
|
2026-05-12 01:04:17 -05:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<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"
|
2026-05-13 18:10:04 -05:00
|
|
|
className={errors.name ? 'border-red-500 focus-visible:ring-red-500' : ''}
|
2026-05-12 01:04:17 -05:00
|
|
|
/>
|
2026-05-13 18:10:04 -05:00
|
|
|
{errors.name && (
|
|
|
|
|
<p className="text-xs text-red-600 mt-1">{errors.name}</p>
|
|
|
|
|
)}
|
2026-05-12 01:04:17 -05:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<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"
|
2026-05-13 18:10:04 -05:00
|
|
|
className={errors.email ? 'border-red-500 focus-visible:ring-red-500' : ''}
|
2026-05-12 01:04:17 -05:00
|
|
|
/>
|
2026-05-13 18:10:04 -05:00
|
|
|
{errors.email && (
|
|
|
|
|
<p className="text-xs text-red-600 mt-1">{errors.email}</p>
|
|
|
|
|
)}
|
2026-05-12 01:04:17 -05:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
|
|
|
|
<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>
|
2026-05-13 18:10:04 -05:00
|
|
|
<textarea
|
2026-05-12 01:04:17 -05:00
|
|
|
id="message"
|
|
|
|
|
name="message"
|
|
|
|
|
value={formState.message}
|
|
|
|
|
onChange={handleChange}
|
|
|
|
|
required
|
|
|
|
|
placeholder="Tell us about your needs..."
|
|
|
|
|
rows={5}
|
2026-05-13 18:10:04 -05:00
|
|
|
className={`flex min-h-[80px] w-full rounded-md border bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ${errors.message ? 'border-red-500 focus-visible:ring-red-500' : ''}`}
|
2026-05-12 01:04:17 -05:00
|
|
|
/>
|
2026-05-13 18:10:04 -05:00
|
|
|
{errors.message && (
|
|
|
|
|
<p className="text-xs text-red-600 mt-1">{errors.message}</p>
|
|
|
|
|
)}
|
2026-05-12 01:04:17 -05:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<Button
|
|
|
|
|
type="submit"
|
|
|
|
|
className="w-full"
|
|
|
|
|
disabled={mutation.isPending}
|
|
|
|
|
>
|
|
|
|
|
{mutation.isPending ? 'Submitting...' : 'Request Consultation'}
|
|
|
|
|
</Button>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default Contact
|