feat(seo): add react-helmet-async, per-page meta/OG tags, JSON-LD, sitemap, robots.txt, heading fixes (#71)

- Added react-helmet-async + HelmetProvider to main.jsx
- Per-page Helmet components on all 8 pages (title, description, OG tags)
- JSON-LD structured data (Organization, LocalBusiness, Service)
- Created public/sitemap.xml with all 17 routes
- Created public/robots.txt
- Fixed heading hierarchy (no h1->h3 skips)
- Improved image alt text throughout
- Fixed docs/zoho-setup.md env defaults clarification
This commit is contained in:
null 2026-05-17 20:03:42 -05:00
parent 2a9eef0e71
commit 1b0d5adc36
15 changed files with 368 additions and 29 deletions

1
.gitignore vendored
View File

@ -34,3 +34,4 @@ pnpm-debug.log*
.vscode/
.idea/
.learnings/
Levi.md

View File

@ -74,15 +74,19 @@ curl -X POST https://accounts.zoho.com/oauth/v2/token \
Add these to your `.env` file:
```env
ZOHO_ENABLED=true
# Zoho integration is OFF by default — set to true to enable
ZOHO_ENABLED=false
ZOHO_API_DOMAIN=https://www.zohoapis.com
ZOHO_ACCOUNTS_DOMAIN=https://accounts.zoho.com
ZOHO_CLIENT_ID=<from Step 1>
ZOHO_CLIENT_SECRET=<from Step 1>
ZOHO_REFRESH_TOKEN=<from Step 3>
ZOHO_CASES_ENABLED=true
# Cases forwarding is also OFF by default
ZOHO_CASES_ENABLED=false
```
> **Note:** Both `ZOHO_ENABLED` and `ZOHO_CASES_ENABLED` default to `false`. Set them to `true` only after completing Steps 13 and verifying your credentials.
### Datacenter Variants
If your Zoho datacenter is **outside the US**, adjust the domains:
@ -165,12 +169,13 @@ Website Contact Form → SQLite (always saved)
## What Happens Next?
After configuration, your team (Ripley + Bishop) will:
1. Deploy environment variables to production
2. Run integration tests
3. Verify data flows to Zoho CRM
4. Update `PROJECT.md` with the integration status
After configuration:
1. Deploy the environment variables to production
2. Set `ZOHO_ENABLED=true` and `ZOHO_CASES_ENABLED=true` in production `.env`
3. Restart the application
4. Submit a test lead and support case to verify data flows to Zoho CRM
5. Check Zoho CRM Leads and Cases tabs to confirm both appear
---
**Questions?** Contact Ripley (Infrastructure) or Neo (Backend).
**Need help?** Contact your site administrator.

53
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "queuenorth-website",
"version": "0.4.8",
"version": "0.6.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "queuenorth-website",
"version": "0.4.8",
"version": "0.6.6",
"dependencies": {
"@radix-ui/react-dialog": "^1.1.0",
"@radix-ui/react-visually-hidden": "^1.2.4",
@ -19,6 +19,7 @@
"lucide-react": "^0.468.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-helmet-async": "^3.0.0",
"react-router-dom": "^7.1.3",
"sonner": "^1.7.0",
"tailwindcss-animate": "^1.0.7",
@ -3132,6 +3133,15 @@
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"license": "ISC"
},
"node_modules/invariant": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.0.0"
}
},
"node_modules/ip-address": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
@ -3230,7 +3240,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true,
"license": "MIT"
},
"node_modules/jsesc": {
@ -3277,6 +3286,18 @@
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"license": "MIT"
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@ -3913,6 +3934,26 @@
"react": "^19.2.6"
}
},
"node_modules/react-fast-compare": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
"integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==",
"license": "MIT"
},
"node_modules/react-helmet-async": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-3.0.0.tgz",
"integrity": "sha512-nA3IEZfXiclgrz4KLxAhqJqIfFDuvzQwlKwpdmzZIuC1KNSghDEIXmyU0TKtbM+NafnkICcwx8CECFrZ/sL/1w==",
"license": "Apache-2.0",
"dependencies": {
"invariant": "^2.2.4",
"react-fast-compare": "^3.2.2",
"shallowequal": "^1.1.0"
},
"peerDependencies": {
"react": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/react-refresh": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
@ -4305,6 +4346,12 @@
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
"node_modules/shallowequal": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
"license": "MIT"
},
"node_modules/shell-quote": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",

View File

@ -29,6 +29,7 @@
"lucide-react": "^0.468.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-helmet-async": "^3.0.0",
"react-router-dom": "^7.1.3",
"sonner": "^1.7.0",
"tailwindcss-animate": "^1.0.7",

4
public/robots.txt Normal file
View File

@ -0,0 +1,4 @@
User-agent: *
Allow: /
Sitemap: https://queuenorth.com/sitemap.xml

88
public/sitemap.xml Normal file
View File

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://queuenorth.com</loc>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://queuenorth.com/about</loc>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://queuenorth.com/services</loc>
<changefreq>monthly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://queuenorth.com/services/unified-communications</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://queuenorth.com/services/contact-center</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://queuenorth.com/services/managed-support</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://queuenorth.com/services/consulting-training</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://queuenorth.com/services/infrastructure-cabling</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://queuenorth.com/services/wireless-access</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://queuenorth.com/services/local-networking</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://queuenorth.com/industries</loc>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://queuenorth.com/industries/healthcare</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://queuenorth.com/industries/retail</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://queuenorth.com/industries/manufacturing</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://queuenorth.com/industries/education-finance</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://queuenorth.com/contact</loc>
<changefreq>monthly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://queuenorth.com/support</loc>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
</urlset>

View File

@ -3,6 +3,7 @@ import { createRoot } from 'react-dom/client'
import { RouterProvider } from 'react-router-dom'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { Toaster } from 'sonner'
import { HelmetProvider } from 'react-helmet-async'
import router from './router.jsx'
import App from './App.jsx'
@ -17,10 +18,12 @@ const queryClient = new QueryClient({
// Wrap the router with providers
const Root = () => (
<StrictMode>
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
<Toaster position="top-right" />
</QueryClientProvider>
<HelmetProvider>
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
<Toaster position="top-right" />
</QueryClientProvider>
</HelmetProvider>
</StrictMode>
)

View File

@ -1,8 +1,19 @@
import { Helmet } from 'react-helmet-async'
import { Link } from 'react-router-dom'
const About = () => {
return (
<>
<Helmet>
<title>About Queue North | Veteran-Owned 8x8 Partner 25+ Years of Service</title>
<meta name="description" content="Queue North Technologies is a veteran-owned 8x8 and Cisco Certified Partner with 25+ years serving businesses in Houghton, MI and the Upper Peninsula. Learn our story, values, and expertise." />
<meta property="og:title" content="About Queue North | Veteran-Owned 8x8 Partner — 25+ Years of Service" />
<meta property="og:description" content="Veteran-owned 8x8 and Cisco Certified Partner with 25+ years serving Houghton, MI and the Upper Peninsula." />
<meta property="og:url" content="https://queuenorth.com/about" />
<meta property="og:type" content="website" />
<meta property="og:image" content="https://queuenorth.com/logo.svg" />
<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">
@ -38,7 +49,7 @@ const About = () => {
<div className="rounded-xl overflow-hidden shadow-lg">
<img
src="/assets/about-image.png"
alt="Our Team"
alt="Queue North Technologies team providing business communications solutions"
className="w-full max-h-96 h-auto object-cover"
/>
</div>

View File

@ -1,3 +1,4 @@
import { Helmet } from 'react-helmet-async'
import { useState } from 'react'
import { useMutation } from '@tanstack/react-query'
import { toast } from 'sonner'
@ -102,6 +103,16 @@ const Contact = () => {
return (
<>
<Helmet>
<title>Contact Queue North | Schedule a Consultation</title>
<meta name="description" content="Contact Queue North Technologies to schedule a free consultation. Call (906) 482-6616 or fill out our form for business phone, UCaaS, IT support, and networking solutions." />
<meta property="og:title" content="Contact Queue North | Schedule a Consultation" />
<meta property="og:description" content="Schedule a free consultation with Queue North Technologies. Business communications and IT solutions in Houghton, MI." />
<meta property="og:url" content="https://queuenorth.com/contact" />
<meta property="og:type" content="website" />
<meta property="og:image" content="https://queuenorth.com/logo.svg" />
<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">

View File

@ -1,3 +1,4 @@
import { Helmet } from 'react-helmet-async'
import { Button } from '@/components/ui/Button'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/Card'
import { services } from '@/data/services'
@ -16,8 +17,71 @@ const industryIcons = {
const Home = () => {
const navigate = useNavigate()
const organizationLd = {
'@context': 'https://schema.org',
'@type': 'Organization',
name: 'Queue North Technologies',
url: 'https://queuenorth.com',
logo: 'https://queuenorth.com/logo.svg',
description: 'Business communications and IT partner — Houghton, MI. 8x8 Certified Partner, Veteran Owned, 25+ years of service.',
address: {
'@type': 'PostalAddress',
addressLocality: 'Houghton',
addressRegion: 'MI',
addressCountry: 'US',
},
contactPoint: {
'@type': 'ContactPoint',
telephone: '+1-906-482-6616',
contactType: 'customer service',
areaServed: 'US',
},
sameAs: [],
}
const localBusinessLd = {
'@context': 'https://schema.org',
'@type': 'LocalBusiness',
'@id': 'https://queuenorth.com/#business',
name: 'Queue North Technologies',
image: 'https://queuenorth.com/logo.svg',
url: 'https://queuenorth.com',
telephone: '+1-906-482-6616',
email: 'info@queuenorth.com',
address: {
'@type': 'PostalAddress',
streetAddress: 'Houghton',
addressLocality: 'Houghton',
addressRegion: 'MI',
addressCountry: 'US',
},
areaServed: {
'@type': 'Place',
name: 'Upper Peninsula, Michigan',
},
priceRange: '$$',
openingHoursSpecification: {
'@type': 'OpeningHoursSpecification',
dayOfWeek: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
opens: '08:00',
closes: '18:00',
},
}
return (
<>
<Helmet>
<title>Queue North Technologies | Business Communications & IT Partner Houghton, MI</title>
<meta name="description" content="Queue North Technologies is a veteran-owned 8x8 Certified Partner providing business phone systems, UCaaS, contact center, IT support, and networking solutions in Houghton, MI and the Upper Peninsula. 25+ years of proven reliability." />
<meta property="og:title" content="Queue North Technologies | Business Communications & IT Partner — Houghton, MI" />
<meta property="og:description" content="Veteran-owned 8x8 Certified Partner. Business phone, UCaaS, contact center, IT support, and networking solutions. 25+ years serving Houghton, MI and the Upper Peninsula." />
<meta property="og:url" content="https://queuenorth.com" />
<meta property="og:type" content="website" />
<meta property="og:image" content="https://queuenorth.com/logo.svg" />
<meta property="og:site_name" content="Queue North Technologies" />
<script type="application/ld+json">{JSON.stringify(organizationLd)}</script>
<script type="application/ld+json">{JSON.stringify(localBusinessLd)}</script>
</Helmet>
{/* Hero Section */}
<section className="bg-gradient-to-br from-primary-navy via-primary-navy to-teal-900 text-white py-16 md:py-24">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
@ -39,7 +103,7 @@ const Home = () => {
</div>
<div className="flex flex-wrap gap-4">
<div className="flex items-center gap-2 px-4 py-2 bg-white/20 rounded-lg text-sm font-medium">
<img src="/assets/8x8_Logo_White.svg" alt="8x8" className="h-5 w-5" />
<img src="/assets/8x8_Logo_White.svg" alt="8x8 Certified Partner logo" className="h-5 w-5" />
<span>8x8 Certified Partner</span>
</div>
<div className="flex items-center gap-2 px-4 py-2 bg-white/20 rounded-lg text-sm font-medium">
@ -59,7 +123,7 @@ const Home = () => {
<div className="relative rounded-xl overflow-hidden shadow-2xl">
<img
src="/assets/hero-tech.png"
alt="Communications Infrastructure"
alt="Business communications and IT infrastructure solutions by Queue North Technologies"
className="w-full h-full object-cover"
/>
<div className="absolute inset-0 bg-gradient-to-t from-primary-navy/50 to-transparent" />
@ -78,7 +142,7 @@ const Home = () => {
</div>
<div className="flex flex-wrap justify-center items-center gap-8 md:gap-16 opacity-70">
<div className="flex items-center gap-3">
<img src="/assets/8x8_Logo_White.svg" alt="8x8" className="h-8" />
<img src="/assets/8x8_Logo_White.svg" alt="8x8 Certified Partner logo" className="h-8" />
<span className="font-medium">8x8 Certified Partner</span>
</div>
<div className="flex items-center gap-3">
@ -102,7 +166,7 @@ const Home = () => {
<div className="flex-1">
<img
src="/assets/JointLogoWhite.png"
alt="Joint Logo White - 8x8 and Cisco Partner"
alt="8x8 and Cisco Certified Partner logo for Queue North Technologies"
className="max-h-32 w-auto object-contain mx-auto"
/>
</div>
@ -141,7 +205,7 @@ const Home = () => {
<div className="bg-white rounded-xl p-8 shadow-lg hover:shadow-xl transition-shadow">
<div className="flex items-center gap-4 mb-4">
<div className="bg-primary-navy text-white p-3 rounded-lg">
<img src="/assets/8x8_Logo_White.svg" alt="8x8" className="h-8 w-auto" />
<img src="/assets/8x8_Logo_White.svg" alt="8x8 Certified Partner logo" className="h-8 w-auto" />
</div>
<div>
<h3 className="text-lg font-semibold text-primary-navy">8x8 Certified Partner</h3>

View File

@ -1,9 +1,20 @@
import { Helmet } from 'react-helmet-async'
import { industries } from '@/data/industries'
import { Link } from 'react-router-dom'
const Industries = () => {
return (
<>
<Helmet>
<title>Industries We Serve | Queue North Technologies</title>
<meta name="description" content="Queue North Technologies serves healthcare, retail, manufacturing, education, and finance industries with tailored communications and IT solutions in Houghton, MI and the Upper Peninsula." />
<meta property="og:title" content="Industries We Serve | Queue North Technologies" />
<meta property="og:description" content="Tailored communications and IT solutions for healthcare, retail, manufacturing, education, and finance." />
<meta property="og:url" content="https://queuenorth.com/industries" />
<meta property="og:type" content="website" />
<meta property="og:image" content="https://queuenorth.com/logo.svg" />
<meta property="og:site_name" content="Queue North Technologies" />
</Helmet>
{/* Page Hero */}
<section className="bg-background py-16 md:py-24">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
@ -30,14 +41,14 @@ const Industries = () => {
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
</div>
<h3 className="text-xl font-semibold text-primary-navy group-hover:text-primary-navy-dark transition-colors">
<h2 className="text-xl font-semibold text-primary-navy group-hover:text-primary-navy-dark transition-colors">
{industry.name}
</h3>
</h2>
</div>
<p className="text-soft-text mb-4">{industry.shortDesc}</p>
<div className="mb-4">
<h4 className="text-sm font-semibold text-text mb-2">Pain Points We Solve</h4>
<h3 className="text-sm font-semibold text-text mb-2">Pain Points We Solve</h3>
<div className="space-y-2">
{industry.painPoints.slice(0, 2).map((painPoint, index) => (
<div key={index} className="flex items-start gap-2 text-sm text-soft-text">

View File

@ -1,3 +1,4 @@
import { Helmet } from 'react-helmet-async'
import { useParams } from 'react-router-dom'
import { industries } from '@/data/industries'
import { Link } from 'react-router-dom'
@ -10,6 +11,10 @@ const IndustryDetail = () => {
if (!industry) {
return (
<section className="bg-background py-16 md:py-24">
<Helmet>
<title>Industry Not Found | Queue North Technologies</title>
<meta name="description" content="The industry page you are looking for does not exist." />
</Helmet>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center">
<h1 className="text-3xl font-bold text-primary-navy mb-4">Industry Not Found</h1>
@ -23,8 +28,22 @@ const IndustryDetail = () => {
)
}
const industryTitle = `${industry.name} | Queue North Technologies`
const industryDesc = industry.shortDesc || `Learn about Queue North Technologies solutions for the ${industry.name} industry in Houghton, MI and the Upper Peninsula.`
const industryUrl = `https://queuenorth.com/industries/${industry.id}`
return (
<>
<Helmet>
<title>{industryTitle}</title>
<meta name="description" content={industryDesc} />
<meta property="og:title" content={industryTitle} />
<meta property="og:description" content={industryDesc} />
<meta property="og:url" content={industryUrl} />
<meta property="og:type" content="website" />
<meta property="og:image" content="https://queuenorth.com/logo.svg" />
<meta property="og:site_name" content="Queue North Technologies" />
</Helmet>
{/* Page Hero */}
<section className="bg-background py-16 md:py-24">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
@ -89,7 +108,7 @@ const IndustryDetail = () => {
</CardHeader>
<CardContent className="space-y-4">
<div>
<h4 className="font-semibold text-text mb-2">Industry</h4>
<h3 className="font-semibold text-text mb-2">Industry</h3>
<p className="text-soft-text">{industry.name}</p>
</div>
<div className="pt-4 border-t border-border">

View File

@ -1,3 +1,4 @@
import { Helmet } from 'react-helmet-async'
import { useParams } from 'react-router-dom'
import { services } from '@/data/services'
import { Link } from 'react-router-dom'
@ -10,6 +11,10 @@ const ServiceDetail = () => {
if (!service) {
return (
<section className="bg-background py-16 md:py-24">
<Helmet>
<title>Service Not Found | Queue North Technologies</title>
<meta name="description" content="The service page you are looking for does not exist." />
</Helmet>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center">
<h1 className="text-3xl font-bold text-primary-navy mb-4">Service Not Found</h1>
@ -23,8 +28,39 @@ const ServiceDetail = () => {
)
}
const serviceTitle = `${service.name} | Queue North Technologies`
const serviceDesc = service.shortDesc || `Learn about ${service.name} from Queue North Technologies — serving Houghton, MI and the Upper Peninsula.`
const serviceUrl = `https://queuenorth.com/services/${service.id}`
const serviceDetailLd = {
'@context': 'https://schema.org',
'@type': 'Service',
name: service.name,
description: service.shortDesc,
provider: {
'@type': 'Organization',
name: 'Queue North Technologies',
url: 'https://queuenorth.com',
},
areaServed: {
'@type': 'Place',
name: 'Houghton, MI / Upper Peninsula',
},
}
return (
<>
<Helmet>
<title>{serviceTitle}</title>
<meta name="description" content={serviceDesc} />
<meta property="og:title" content={serviceTitle} />
<meta property="og:description" content={serviceDesc} />
<meta property="og:url" content={serviceUrl} />
<meta property="og:type" content="website" />
<meta property="og:image" content="https://queuenorth.com/logo.svg" />
<meta property="og:site_name" content="Queue North Technologies" />
<script type="application/ld+json">{JSON.stringify(serviceDetailLd)}</script>
</Helmet>
{/* Page Hero */}
<section className="bg-background py-16 md:py-24">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
@ -101,11 +137,11 @@ const ServiceDetail = () => {
</CardHeader>
<CardContent className="space-y-4">
<div>
<h4 className="font-semibold text-text mb-2">Service</h4>
<h3 className="font-semibold text-text mb-2">Service</h3>
<p className="text-soft-text">{service.name}</p>
</div>
<div>
<h4 className="font-semibold text-text mb-2">Category</h4>
<h3 className="font-semibold text-text mb-2">Category</h3>
<p className="text-soft-text capitalize">{service.id.replace('-', ' ')}</p>
</div>
<div className="pt-4 border-t border-border">

View File

@ -1,10 +1,37 @@
import { Helmet } from 'react-helmet-async'
import { services } from '@/data/services'
import { MessageCircle, Users, LifeBuoy, GraduationCap, Link as LinkIcon, Wifi, Network } from 'lucide-react'
import { Link } from 'react-router-dom'
const serviceLd = {
'@context': 'https://schema.org',
'@type': 'Service',
serviceType: 'Business Communications and IT Services',
provider: {
'@type': 'Organization',
name: 'Queue North Technologies',
url: 'https://queuenorth.com',
},
areaServed: {
'@type': 'Place',
name: 'Houghton, MI / Upper Peninsula',
},
}
const Services = () => {
return (
<>
<Helmet>
<title>Business Phone, UCaaS & IT Services | Queue North Technologies</title>
<meta name="description" content="Explore Queue North Technologies services: unified communications, contact center, managed IT support, consulting & training, infrastructure cabling, wireless access, and local networking." />
<meta property="og:title" content="Business Phone, UCaaS & IT Services | Queue North Technologies" />
<meta property="og:description" content="Unified communications, contact center, managed IT support, consulting, cabling, wireless, and networking solutions." />
<meta property="og:url" content="https://queuenorth.com/services" />
<meta property="og:type" content="website" />
<meta property="og:image" content="https://queuenorth.com/logo.svg" />
<meta property="og:site_name" content="Queue North Technologies" />
<script type="application/ld+json">{JSON.stringify(serviceLd)}</script>
</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">
@ -35,9 +62,9 @@ const Services = () => {
{service.icon === 'wifi' && <Wifi className="h-6 w-6 text-primary-navy" />}
{service.icon === 'network' && <Network className="h-6 w-6 text-primary-navy" />}
</div>
<h3 className="text-xl font-semibold text-primary-navy group-hover:text-primary-navy-dark transition-colors">
<h2 className="text-xl font-semibold text-primary-navy group-hover:text-primary-navy-dark transition-colors">
{service.name}
</h3>
</h2>
</div>
<p className="text-soft-text mb-4">{service.shortDesc}</p>
<div className="space-y-2 mb-4">

View File

@ -1,3 +1,4 @@
import { Helmet } from 'react-helmet-async'
import { useState } from 'react'
import { useMutation } from '@tanstack/react-query'
import { toast } from 'sonner'
@ -105,6 +106,16 @@ const Support = () => {
return (
<>
<Helmet>
<title>IT Support & Help Desk | Queue North Technologies</title>
<meta name="description" content="Get IT support and help desk services from Queue North Technologies. 24/7 monitoring, rapid response SLAs, and dedicated support engineers for your business." />
<meta property="og:title" content="IT Support & Help Desk | Queue North Technologies" />
<meta property="og:description" content="24/7 IT support and help desk services. Rapid response SLAs, dedicated support engineers." />
<meta property="og:url" content="https://queuenorth.com/support" />
<meta property="og:type" content="website" />
<meta property="og:image" content="https://queuenorth.com/logo.svg" />
<meta property="og:site_name" content="Queue North Technologies" />
</Helmet>
{/* Page Hero */}
<section className="bg-background py-16 md:py-24">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">