fix: remove all email addresses from site, redesign contact page, update footer, about, header, support (#165 #172 #134 #173)
This commit is contained in:
parent
b04e5bbb04
commit
928527b908
|
|
@ -6,8 +6,8 @@ const Footer = () => {
|
|||
const companyInfo = {
|
||||
name: 'Queue North',
|
||||
tagline: 'Modern communications infrastructure without the vendor noise.',
|
||||
address: 'Orlando, FL',
|
||||
email: 'info@queuenorth.com',
|
||||
addressLine1: '7901 4th St N',
|
||||
addressLine2: 'St. Petersburg, FL 33702',
|
||||
phone: '(321) 730-8020',
|
||||
tollFree: '(888) 656-2850',
|
||||
}
|
||||
|
|
@ -53,11 +53,17 @@ const Footer = () => {
|
|||
<span className="font-bold text-xl tracking-tight text-white">Queue North</span>
|
||||
</Link>
|
||||
<p className="text-navy-light text-sm leading-relaxed mb-5">{companyInfo.tagline}</p>
|
||||
<p className="text-navy-light text-sm mb-3">{companyInfo.address}</p>
|
||||
<a
|
||||
href="https://maps.google.com/?q=7901+4th+St+N+St+Petersburg+FL+33702"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block text-navy-light text-sm mb-3 hover:text-primary-cyan transition-colors leading-relaxed"
|
||||
aria-label="View Queue North office on Google Maps"
|
||||
>
|
||||
<span className="block">{companyInfo.addressLine1}</span>
|
||||
<span className="block">{companyInfo.addressLine2}</span>
|
||||
</a>
|
||||
<div className="space-y-2 text-navy-light text-sm mb-6">
|
||||
<div>
|
||||
<a href={`mailto:${companyInfo.email}`} className="hover:text-primary-cyan transition-colors" aria-label={`Send email to ${companyInfo.email}`}>{companyInfo.email}</a>
|
||||
</div>
|
||||
<div>
|
||||
<a href={`tel:+1${companyInfo.phone.replace(/\D/g, '')}`} className="hover:text-primary-cyan transition-colors" aria-label={`Call ${companyInfo.phone}`}>{companyInfo.phone}</a>
|
||||
</div>
|
||||
|
|
@ -143,9 +149,30 @@ const Footer = () => {
|
|||
© {currentYear} Queue North Technologies. All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-6">
|
||||
<a href="https://linkedin.com/company/queue-north-technologies-llc" target="_blank" rel="noopener noreferrer" className="text-primary-cyan hover:text-white transition-colors text-sm font-medium" aria-label="Follow Queue North on LinkedIn">
|
||||
LinkedIn
|
||||
<div className="flex items-center gap-4">
|
||||
<a
|
||||
href="https://linkedin.com/company/queue-north-technologies-llc"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-navy-light hover:text-primary-cyan transition-colors"
|
||||
aria-label="Follow Queue North on LinkedIn"
|
||||
>
|
||||
<svg className="h-5 w-5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
|
||||
</svg>
|
||||
<span className="sr-only">LinkedIn</span>
|
||||
</a>
|
||||
<a
|
||||
href="https://www.facebook.com/QueueNorth"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-navy-light hover:text-primary-cyan transition-colors"
|
||||
aria-label="Follow Queue North on Facebook"
|
||||
>
|
||||
<svg className="h-5 w-5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/>
|
||||
</svg>
|
||||
<span className="sr-only">Facebook</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -130,59 +130,98 @@ const Header = () => {
|
|||
<VisuallyHidden.Root asChild>
|
||||
<SheetTitle>Navigation Menu</SheetTitle>
|
||||
</VisuallyHidden.Root>
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<Link to="/" onClick={closeMobileMenu} className="flex items-center gap-3" aria-label="Queue North Technologies Home">
|
||||
<img src="/logo.png" alt="Queue North" className="h-14 w-auto brightness-0 invert" />
|
||||
<span className="font-bold text-2xl">Queue North</span>
|
||||
|
||||
{/* Logo */}
|
||||
<div className="flex items-center gap-3 pb-4 border-b border-white/10">
|
||||
<Link to="/" onClick={closeMobileMenu} className="flex items-center gap-3" aria-label="Queue North Technologies Home">
|
||||
<img src="/logo.png" alt="Queue North" className="h-10 w-auto brightness-0 invert" />
|
||||
<span className="font-bold text-xl tracking-tight">Queue North</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Scrollable nav */}
|
||||
<nav className="flex-1 min-h-0 overflow-y-auto" aria-label="Mobile navigation">
|
||||
{/* Main links — Services and Industries handled as sections below */}
|
||||
<ul className="space-y-1 mb-6">
|
||||
{navLinks
|
||||
.filter(link => link.name !== 'Services' && link.name !== 'Industries')
|
||||
.map((link) => (
|
||||
<li key={link.name}>
|
||||
<Link
|
||||
to={link.href}
|
||||
onClick={closeMobileMenu}
|
||||
className={`flex items-center py-3 px-2 rounded-md text-base font-medium transition-colors ${isActive(link.href) ? 'text-white bg-white/10' : 'text-white/70 hover:text-white hover:bg-white/5'}`}
|
||||
>
|
||||
{link.name}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{/* Services section */}
|
||||
<div className="mb-6">
|
||||
<Link
|
||||
to="/services"
|
||||
onClick={closeMobileMenu}
|
||||
className={`block py-2 px-2 text-xs font-semibold uppercase tracking-wider mb-1 transition-colors ${isActive('/services') ? 'text-primary-cyan' : 'text-primary-cyan/80 hover:text-primary-cyan'}`}
|
||||
>
|
||||
Services
|
||||
</Link>
|
||||
<ul className="space-y-1">
|
||||
{serviceLinks.map((service) => (
|
||||
<li key={service.name}>
|
||||
<Link
|
||||
to={service.href}
|
||||
onClick={closeMobileMenu}
|
||||
className={`flex items-center py-3 px-4 rounded-md text-sm transition-colors ${isActive(service.href) ? 'text-white font-semibold bg-white/10' : 'text-white/70 hover:text-white hover:bg-white/5'}`}
|
||||
aria-label={service.name}
|
||||
>
|
||||
{service.name}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<nav className="flex flex-col space-y-6" aria-label="Mobile navigation">
|
||||
<div>
|
||||
<ul className="space-y-2">
|
||||
{navLinks.map((link) => (
|
||||
<li key={link.name}>
|
||||
<Link to={link.href} onClick={closeMobileMenu} className={`block text-base font-medium py-2 ${isActive(link.href) ? 'text-white font-semibold' : 'text-white/70 hover:text-white'} transition-colors`} aria-label={link.name}>
|
||||
{link.name}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="text-xs font-semibold uppercase tracking-wider text-white/60 mb-3" aria-hidden="true">Services</h4>
|
||||
<ul className="space-y-2">
|
||||
{serviceLinks.map((service) => (
|
||||
<li key={service.name}>
|
||||
<Link to={service.href} onClick={closeMobileMenu} className={`block text-sm py-2 border-b border-white/10 last:border-0 transition-colors ${isActive(service.href) ? 'text-white font-semibold' : 'text-white/70 hover:text-white'}`} aria-label={'Learn about ' + service.name}>
|
||||
{service.name}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="text-xs font-semibold uppercase tracking-wider text-white/60 mb-3" aria-hidden="true">Industries</h4>
|
||||
<ul className="space-y-2">
|
||||
{industryLinks.map((industry) => (
|
||||
<li key={industry.name}>
|
||||
<Link to={industry.href} onClick={closeMobileMenu} className={`block text-sm py-2 border-b border-white/10 last:border-0 transition-colors ${isActive(industry.href) ? 'text-white font-semibold' : 'text-white/70 hover:text-white'}`} aria-label={'Learn about ' + industry.name + ' industry solutions'}>
|
||||
{industry.name}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div className="mt-auto pt-6">
|
||||
<Link to="/contact" onClick={closeMobileMenu} className="inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 w-full bg-primary-cyan text-primary-navy hover:bg-cyan-600 transition-colors" aria-label="Request a consultation">
|
||||
Request Consultation
|
||||
{/* Industries section */}
|
||||
<div>
|
||||
<Link
|
||||
to="/industries"
|
||||
onClick={closeMobileMenu}
|
||||
className={`block py-2 px-2 text-xs font-semibold uppercase tracking-wider mb-1 transition-colors ${isActive('/industries') ? 'text-primary-cyan' : 'text-primary-cyan/80 hover:text-primary-cyan'}`}
|
||||
>
|
||||
Industries
|
||||
</Link>
|
||||
<ul className="space-y-1">
|
||||
{industryLinks.map((industry) => (
|
||||
<li key={industry.name}>
|
||||
<Link
|
||||
to={industry.href}
|
||||
onClick={closeMobileMenu}
|
||||
className={`flex items-center py-3 px-4 rounded-md text-sm transition-colors ${isActive(industry.href) ? 'text-white font-semibold bg-white/10' : 'text-white/70 hover:text-white hover:bg-white/5'}`}
|
||||
aria-label={industry.name}
|
||||
>
|
||||
{industry.name}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{/* CTA */}
|
||||
<div className="pt-4 border-t border-white/10">
|
||||
<Link
|
||||
to="/contact"
|
||||
onClick={closeMobileMenu}
|
||||
className="inline-flex items-center justify-center gap-2 w-full rounded-md text-sm font-semibold h-11 px-4 bg-primary-cyan text-primary-navy hover:bg-white transition-colors duration-200"
|
||||
aria-label="Get a free quote"
|
||||
>
|
||||
Get a Free Quote
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M17 8l4 4m0 0l-4 4m4-4H3" />
|
||||
</svg>
|
||||
</Link>
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ const SheetContent = React.forwardRef(({ className, children, side = 'right', ..
|
|||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-[#F8FAFC] transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-primary-navy focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-section-alt">
|
||||
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm text-white opacity-60 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-primary-navy disabled:pointer-events-none">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</SheetPrimitive.Close>
|
||||
|
|
|
|||
|
|
@ -47,10 +47,10 @@ const About = () => {
|
|||
</div>
|
||||
<div>
|
||||
<div className="rounded-xl overflow-hidden shadow-lg">
|
||||
<img
|
||||
<img
|
||||
src="/assets/about-image.webp"
|
||||
alt="Queue North Technologies team providing business communications solutions"
|
||||
className="w-full max-h-96 h-auto object-cover"
|
||||
alt="Compass representing Queue North's guiding philosophy"
|
||||
className="w-full h-72 object-cover object-top"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -58,6 +58,43 @@ const About = () => {
|
|||
</div>
|
||||
</section>
|
||||
|
||||
{/* The Meaning Behind Queue North */}
|
||||
<section className="bg-primary-navy py-20">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-white mb-4">The Meaning Behind Queue North</h2>
|
||||
<p className="text-lg text-white/70 max-w-2xl mx-auto leading-relaxed">
|
||||
Queue is stability. North is responsible progress. The compass is how we guide customers with both.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-4xl mx-auto">
|
||||
<div className="bg-white/10 border border-white/20 rounded-xl p-8">
|
||||
<div className="w-12 h-12 bg-primary-cyan/20 rounded-lg flex items-center justify-center mb-5">
|
||||
<svg className="h-6 w-6 text-primary-cyan" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||
<rect x="7" y="7" width="10" height="10" rx="1" strokeLinejoin="round" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 7V5M12 7V5M15 7V5M9 17v2M12 17v2M15 17v2M7 9H5M7 12H5M7 15H5M17 9h2M17 12h2M17 15h2" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-primary-cyan mb-3">Queue</h3>
|
||||
<p className="text-white/80 leading-relaxed">
|
||||
Rooted in technology that works — proven, stable, and repeatable. We focus on environments that behave predictably in real production conditions.
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-white/10 border border-white/20 rounded-xl p-8">
|
||||
<div className="w-12 h-12 bg-primary-cyan/20 rounded-lg flex items-center justify-center mb-5">
|
||||
<svg className="h-6 w-6 text-primary-cyan" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M5 10l7-7m0 0l7 7m-7-7v18" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-primary-cyan mb-3">North</h3>
|
||||
<p className="text-white/80 leading-relaxed">
|
||||
A <strong className="text-white font-semibold">responsible direction forward</strong> — evaluating and adopting new technology carefully, with governance, security, and long-term ownership in mind.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Our Values */}
|
||||
<section className="bg-background py-16">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ 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 { Link } from 'react-router-dom'
|
||||
import { submitLead } from '@/lib/api'
|
||||
import { useDebounce } from '@/hooks/useDebounce'
|
||||
|
||||
|
|
@ -18,7 +17,7 @@ const Contact = () => {
|
|||
zip: '',
|
||||
message: '',
|
||||
service_interest: '',
|
||||
company_website: '', // Honeypot field - hidden from humans, bots will fill it
|
||||
company_website: '',
|
||||
})
|
||||
const [errors, setErrors] = useState({
|
||||
company: '',
|
||||
|
|
@ -26,39 +25,26 @@ const Contact = () => {
|
|||
email: '',
|
||||
message: '',
|
||||
})
|
||||
// Debounce validation errors so they don't flash on every keystroke
|
||||
const debouncedErrors = useDebounce(errors, 300)
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
|
||||
const validateForm = () => {
|
||||
const newErrors = {
|
||||
company: '',
|
||||
name: '',
|
||||
email: '',
|
||||
message: '',
|
||||
}
|
||||
|
||||
// Validate required fields
|
||||
const newErrors = { company: '', name: '', email: '', message: '' }
|
||||
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
|
||||
}
|
||||
|
||||
|
|
@ -72,26 +58,12 @@ const Contact = () => {
|
|||
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: '',
|
||||
})
|
||||
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.")
|
||||
toast.success("We already have your submission! We'll be in touch.")
|
||||
} else {
|
||||
toast.error(error.message || 'Failed to submit form. Please try again.')
|
||||
}
|
||||
|
|
@ -103,12 +75,62 @@ const Contact = () => {
|
|||
const handleChange = (e) => {
|
||||
const { name, value } = e.target
|
||||
setFormState(prev => ({ ...prev, [name]: value }))
|
||||
// Clear error for this field as user types
|
||||
if (errors[name]) {
|
||||
setErrors(prev => ({ ...prev, [name]: '' }))
|
||||
}
|
||||
if (errors[name]) setErrors(prev => ({ ...prev, [name]: '' }))
|
||||
}
|
||||
|
||||
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 4th 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',
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
|
|
@ -121,93 +143,65 @@ const Contact = () => {
|
|||
<meta property="og:image" content="https://queuenorth.com/assets/og-image.png" />
|
||||
<meta property="og:site_name" content="Queue North Technologies" />
|
||||
</Helmet>
|
||||
{/* Page Hero */}
|
||||
<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.
|
||||
</p>
|
||||
<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>
|
||||
<a href="tel:+13217308020" className="text-sm font-medium hover:underline" aria-label="Call (321) 730-8020">(321) 730-8020</a>
|
||||
</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>
|
||||
<a href="tel:+18886562850" className="text-sm font-medium hover:underline" aria-label="Call (888) 656-2850">(888) 656-2850</a>
|
||||
</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 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>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{/* 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>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Contact Form */}
|
||||
<section className="bg-background py-16">
|
||||
{/* Contact Body */}
|
||||
<section className="bg-background py-16 lg:py-20">
|
||||
<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 */}
|
||||
<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 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>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-text mb-2">Phone</h3>
|
||||
<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>
|
||||
</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>
|
||||
|
||||
<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" />
|
||||
<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" />
|
||||
</svg>
|
||||
{item}
|
||||
{point}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right - Form */}
|
||||
<div>
|
||||
<form onSubmit={handleSubmit} noValidate className={`space-y-6 ${isSubmitting ? 'opacity-70 pointer-events-none' : ''}`}>
|
||||
{/* 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 */}
|
||||
<div>
|
||||
<label htmlFor="company" className="block text-sm font-medium text-text mb-2">
|
||||
Company Name <span className="text-red-600">*</span>
|
||||
<label htmlFor="company" className="block text-sm font-medium text-text mb-1.5">
|
||||
Company Name <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
|
|
@ -217,103 +211,88 @@ const Contact = () => {
|
|||
onChange={handleChange}
|
||||
required
|
||||
placeholder="Your company name"
|
||||
className={errors.company ? 'border-red-500 focus-visible:ring-red-500' : ''}
|
||||
className={debouncedErrors.company ? 'border-red-500 focus-visible:ring-red-500' : ''}
|
||||
/>
|
||||
{debouncedErrors.company && (
|
||||
<p className="text-xs text-red-600 mt-1">{debouncedErrors.company}</p>
|
||||
)}
|
||||
{debouncedErrors.company && <p className="text-xs text-red-500 mt-1">{debouncedErrors.company}</p>}
|
||||
</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"
|
||||
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>
|
||||
)}
|
||||
{/* 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>
|
||||
</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"
|
||||
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>
|
||||
)}
|
||||
{/* 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>
|
||||
</div>
|
||||
|
||||
{/* Message */}
|
||||
<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 htmlFor="message" className="block text-sm font-medium text-text mb-1.5">
|
||||
Message <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<Textarea
|
||||
id="message"
|
||||
|
|
@ -322,14 +301,13 @@ const Contact = () => {
|
|||
onChange={handleChange}
|
||||
required
|
||||
placeholder="Tell us about your needs..."
|
||||
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' : ''}`}
|
||||
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' : ''}`}
|
||||
rows={5}
|
||||
/>
|
||||
{debouncedErrors.message && (
|
||||
<p className="text-xs text-red-600 mt-1">{debouncedErrors.message}</p>
|
||||
)}
|
||||
{debouncedErrors.message && <p className="text-xs text-red-500 mt-1">{debouncedErrors.message}</p>}
|
||||
</div>
|
||||
|
||||
{/* Honeypot */}
|
||||
<div className="absolute opacity-0 h-0 overflow-hidden" aria-hidden="true">
|
||||
<input
|
||||
type="text"
|
||||
|
|
@ -341,21 +319,17 @@ const Contact = () => {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full"
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
<Button type="submit" className="w-full h-11" disabled={isSubmitting}>
|
||||
{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...
|
||||
Sending...
|
||||
</>
|
||||
) : (
|
||||
'Request Consultation'
|
||||
'Send Message'
|
||||
)}
|
||||
</Button>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -43,7 +43,6 @@ const Home = () => {
|
|||
image: 'https://queuenorth.com/assets/og-image.png',
|
||||
url: 'https://queuenorth.com',
|
||||
telephone: '+1-321-730-8020',
|
||||
email: 'info@queuenorth.com',
|
||||
address: {
|
||||
'@type': 'PostalAddress',
|
||||
addressCountry: 'US',
|
||||
|
|
|
|||
|
|
@ -134,12 +134,6 @@ const Support = () => {
|
|||
</svg>
|
||||
<p className="text-sm font-medium"><a href="tel:+13217308020" className="text-primary-cyan hover:text-cyan-700 underline" aria-label="Call (321) 730-8020">(321) 730-8020</a></p>
|
||||
</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 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>
|
||||
<p className="text-sm font-medium"><a href="mailto:info@queuenorth.com" className="text-primary-cyan hover:text-cyan-700 underline" aria-label="Send email to info@queuenorth.com">info@queuenorth.com</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-8">
|
||||
<Link to="/contact" className="inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-6 bg-primary-navy text-white hover:bg-primary-navy-dark transition-colors">
|
||||
|
|
|
|||
Loading…
Reference in New Issue