diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..77d9f6e --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +AMAZON_USER= +AMAZON_PASSWORD= +CONTACT_TO_EMAIL=info@bayareaaffiliates.com +CONTACT_FROM_EMAIL=info@bayareaaffiliates.com +PORT=8080 diff --git a/.gitignore b/.gitignore index 4108b33..f2ad80b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,10 +7,13 @@ yarn-error.log* pnpm-debug.log* lerna-debug.log* -node_modules -dist -dist-ssr -*.local +node_modules +dist +dist-ssr +.env +.env.* +!.env.example +*.local # Editor directories and files .vscode/* diff --git a/App.tsx b/App.tsx index 93d049c..5facbe1 100644 --- a/App.tsx +++ b/App.tsx @@ -19,6 +19,7 @@ const ContactPage = React.lazy(() => import('./src/pages/ContactPage')); const LocationsPage = React.lazy(() => import('./src/pages/LocationsPage')); const PrivacyPolicyPage = React.lazy(() => import('./src/pages/PrivacyPolicyPage')); const TermsOfServicePage = React.lazy(() => import('./src/pages/TermsOfServicePage')); +const NotFoundPage = React.lazy(() => import('./src/pages/NotFoundPage')); // Grain Overlay Component const GrainOverlay = () => ( @@ -142,6 +143,8 @@ const AppContent: React.FC = () => { } /> ))} + + } /> diff --git a/Dockerfile b/Dockerfile index 97b954d..6647c55 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,9 +8,16 @@ RUN npm ci COPY . . RUN npm run build -FROM caddy:2.8-alpine +FROM node:22-alpine AS runtime -COPY Caddyfile /etc/caddy/Caddyfile -COPY --from=build /app/dist /srv +WORKDIR /app -EXPOSE 80 +COPY package.json package-lock.json ./ +RUN npm ci --omit=dev + +COPY server.mjs ./ +COPY --from=build /app/dist ./dist + +EXPOSE 8080 + +CMD ["node", "server.mjs"] diff --git a/Modern_business_email_202604201852.png b/Modern_business_email_202604201852.png new file mode 100644 index 0000000..8140402 Binary files /dev/null and b/Modern_business_email_202604201852.png differ diff --git a/README.md b/README.md index 9d1f2a1..95e1890 100644 --- a/README.md +++ b/README.md @@ -7,23 +7,39 @@ Vite/React marketing site for `bayareait.services`. ```bash npm install npm run dev +npm run dev:api ``` +`npm run dev` starts the frontend on `http://127.0.0.1:3012`. Run `npm run dev:api` in a second terminal so the contact form can post to the local API on `http://127.0.0.1:3013`. + ## Production build ```bash npm run build +npm start ``` The production build regenerates `robots.txt` and `sitemap.xml`, runs Vite, prunes unused assets, and prerenders route HTML into `dist/`. +## Environment variables + +```bash +AMAZON_USER=... +AMAZON_PASSWORD=... +CONTACT_TO_EMAIL=info@bayareaaffiliates.com +CONTACT_FROM_EMAIL=info@bayareaaffiliates.com +PORT=8080 +``` + +`CONTACT_TO_EMAIL` and `CONTACT_FROM_EMAIL` are optional and default to `info@bayareaaffiliates.com`. + ## Docker ```bash docker compose up --build -d ``` -The container serves the built site on `http://127.0.0.1:8080`. +The container serves the built site and the `/api/contact` endpoint on `http://127.0.0.1:8080`. ## Reverse proxy with host Caddy @@ -36,6 +52,6 @@ bayareait.services { ## Launch notes -- Static assets are served by Caddy inside the container. +- Static assets and the contact form endpoint are served by the Node app inside the container. - SPA fallback is enabled while prerendered route folders still resolve directly. - Claims such as `24/7`, `30+ local businesses`, and legal company details should be verified before launch. diff --git a/components/BackToTop.tsx b/components/BackToTop.tsx index 430c30d..7dc93c3 100644 --- a/components/BackToTop.tsx +++ b/components/BackToTop.tsx @@ -25,16 +25,16 @@ const BackToTop: React.FC = () => { return ( {isVisible && ( - + arrow_upward )} @@ -42,4 +42,4 @@ const BackToTop: React.FC = () => { ); }; -export default BackToTop; \ No newline at end of file +export default BackToTop; diff --git a/components/Blog.tsx b/components/Blog.tsx index c31e95b..a518585 100644 --- a/components/Blog.tsx +++ b/components/Blog.tsx @@ -3,15 +3,15 @@ import { motion } from 'framer-motion'; import gsap from 'gsap'; import { ScrollTrigger } from 'gsap/ScrollTrigger'; import { Link } from 'react-router-dom'; -import { blogPostData } from '../src/data/seoData'; +import { orderedBlogPostData } from '../src/data/seoData'; gsap.registerPlugin(ScrollTrigger); -const posts = blogPostData +const posts = orderedBlogPostData .filter((post) => !post.redirect) .slice(0, 3) .map((post) => ({ - image: post.image || '/images/blog/business-email-comparison-new.png', + image: post.image || '/images/blog/business-email-comparison-new.webp', category: post.category === 'authority' ? 'IT Insights' : 'Local Services', title: post.h1, excerpt: post.description, diff --git a/components/CTA.tsx b/components/CTA.tsx index 1a652be..5b7e450 100644 --- a/components/CTA.tsx +++ b/components/CTA.tsx @@ -31,18 +31,18 @@ const CTA: React.FC = () => { transition={{ delay: 0.2 }} className="flex flex-col sm:flex-row gap-4 justify-center items-center" > - - Book a 20-minute assessment - - - Send a message - + + Book a 20-minute assessment + + + Send a message + { - return ( - -
- -

- Get in Touch -

-

- We're here to help you with all your IT needs. -

-
- - -
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
- - -
-
- - Send Message - -
-
-
-
- ); -}; - -export default Contact; \ No newline at end of file +import React from 'react'; +import { motion } from 'framer-motion'; + +import { useContactForm } from '../src/hooks/useContactForm'; + +const Contact: React.FC = () => { + const { errorMessage, hasError, isSubmitted, isSubmitting, resetFeedback, submitContactForm } = useContactForm(); + + return ( + +
+ +

+ Get in Touch +

+

+ We're here to help you with all your IT needs. +

+
+ + + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + {isSubmitting ? 'Sending...' : 'Send Message'} + +
+ {isSubmitted && ( +

+ Thanks. Your message was sent successfully. +

+ )} + {hasError && ( +

+ {errorMessage} +

+ )} +
+
+
+ ); +}; + +export default Contact; diff --git a/components/Footer.tsx b/components/Footer.tsx index a0e9a1e..0f95295 100644 --- a/components/Footer.tsx +++ b/components/Footer.tsx @@ -13,7 +13,7 @@ const Footer: React.FC = () => { Bay Area IT

- Providing reliable IT services and practical technology support to the Coastal Bend community for over 25 years. + Providing reliable IT services and practical technology support to the Coastal Bend community for over 30 years.

@@ -29,11 +29,13 @@ const Footer: React.FC = () => {

Services

    {[ + { label: 'Bay Area Email Services', to: '/services/business-email-services' }, { label: 'IT Help Desk', to: '/services/it-help-desk' }, - { label: 'Computer Support', to: '/services/computer-support' }, - { label: 'Business Email', to: '/services/business-email-services' }, - { label: 'Domain & DNS', to: '/services/domain-registration-dns-support' }, + { label: 'New/Refurbished Desktop Hardware', to: '/services/computer-support' }, + { label: 'Printer & Scanner Installation', to: '/services/printer-scanner-installation' }, { label: 'Web Design', to: '/services/web-design-corpus-christi' }, + { label: 'Shared Drive', to: '/services/shared-drive' }, + { label: 'Network Infrastructure Support', to: '/services/network-infrastructure-support' }, ].map((item) => (
  • diff --git a/components/Hero.tsx b/components/Hero.tsx index ec8cbc3..7b41549 100644 --- a/components/Hero.tsx +++ b/components/Hero.tsx @@ -110,13 +110,13 @@ const Hero: React.FC = () => {
    - Serving the Coastal Bend since 2000 + Serving the Coastal Bend since 1996

    Reliable IT Services
    - for Over 25 Years + for Over 30 Years

    @@ -124,21 +124,21 @@ const Hero: React.FC = () => {

    - - IT Services - - - Get in Touch + + IT Services + + + Get in Touch
    diff --git a/components/Navbar.tsx b/components/Navbar.tsx index 1defa23..9690abf 100644 --- a/components/Navbar.tsx +++ b/components/Navbar.tsx @@ -34,12 +34,12 @@ const Navbar: React.FC = () => { ))}
- - Get IT Support - + + Get IT Support + ); diff --git a/components/Services.tsx b/components/Services.tsx index c592dcb..f9321e7 100644 --- a/components/Services.tsx +++ b/components/Services.tsx @@ -7,32 +7,23 @@ import { Link } from 'react-router-dom'; gsap.registerPlugin(ScrollTrigger); const servicesData = [ - { - id: 1, - category: 'Web Services', - title: 'Web Design', - description: 'Professional websites with domain registration and DNS support to give your business a clean, reliable online presence.', - icon: 'language', - image: '/assets/services/business-it.webp', - href: '/services/web-design-corpus-christi' - }, { id: 2, category: 'Web Services', title: 'Bay Area Email Services', description: 'Enterprise cloud email with 99.99% uptime, local Texas support, 25 GB mailboxes, and business-grade delivery for $5 per inbox.', icon: 'mail', - image: '/assets/services/business-it.webp', + image: '/assets/services/business-email-services.webp', href: '/services/business-email-services' }, { - id: 3, + id: 7, category: 'IT Infrastructure', - title: 'Printer & Scanner Installation', - description: 'Professional installation and configuration of printers and scanners to ensure seamless integration into your workflow.', - icon: 'print', - image: '/assets/services/printer-scanner.webp', - href: '/services' + title: 'IT Help Desk', + description: 'Fast and reliable help desk support for employees, resolving technical issues remotely or on-site.', + icon: 'support_agent', + image: '/assets/services/help-desk.webp', + href: '/services/it-help-desk' }, { id: 4, @@ -44,13 +35,22 @@ const servicesData = [ href: '/services/computer-support' }, { - id: 5, - category: 'Networking', - title: 'Network Infrastructure Support', - description: 'Robust network solutions to ensure connectivity, security, and efficiency, including routers, access points, and switches.', - icon: 'lan', - image: '/assets/services/network-infrastructure.webp', - href: '/services' + id: 3, + category: 'IT Infrastructure', + title: 'Printer & Scanner Installation', + description: 'Professional installation and configuration of printers and scanners to ensure seamless integration into your workflow.', + icon: 'print', + image: '/assets/services/printer-scanner.webp', + href: '/services/printer-scanner-installation' + }, + { + id: 1, + category: 'Web Services', + title: 'Web Design', + description: 'Professional websites with domain registration and DNS support to give your business a clean, reliable online presence.', + icon: 'language', + image: '/assets/services/business-it.webp', + href: '/services/web-design-corpus-christi' }, { id: 6, @@ -59,16 +59,16 @@ const servicesData = [ description: 'Setup and management of shared drive solutions so your team can store, access, and organize files reliably.', icon: 'storage', image: '/assets/services/nas-storage.webp', - href: '/services' + href: '/services/shared-drive' }, { - id: 7, - category: 'IT Infrastructure', - title: 'IT Help Desk', - description: 'Fast and reliable help desk support for employees, resolving technical issues remotely or on-site.', - icon: 'support_agent', - image: '/assets/services/help-desk.webp', - href: '/services/it-help-desk' + id: 5, + category: 'Networking', + title: 'Network Infrastructure Support', + description: 'Robust network solutions to ensure connectivity, security, and efficiency, including routers, access points, and switches.', + icon: 'lan', + image: '/assets/services/network-infrastructure.webp', + href: '/services/network-infrastructure-support' } ]; @@ -203,14 +203,14 @@ const Services: React.FC = ({ preview = false, featuredIds }) =>
- {isRestrictedView && ( -
- + {isRestrictedView && ( +
+
)} diff --git a/components/Testimonials.tsx b/components/Testimonials.tsx index 9ddd5e3..3a425a3 100644 --- a/components/Testimonials.tsx +++ b/components/Testimonials.tsx @@ -1,44 +1,86 @@ -import React from 'react'; -import { motion } from 'framer-motion'; - -const Testimonials: React.FC = () => { - return ( -
-
- - {/* Quote Icon */} -
- format_quote -
- -
- {[1, 2, 3, 4, 5].map((star) => ( - star - ))} -
- -
- "Bay Area IT transformed our IT infrastructure completely. Their proactive approach means we rarely have downtime, and when issues do arise, they're resolved quickly. Our team can focus on patient care instead of tech problems." -
- -
-
- SM -
-
-
Sarah Martinez
-
Operations Manager, Coastal Medical Group
-
-
-
-
-
- ); -}; - -export default Testimonials; +import React from 'react'; +import { motion } from 'framer-motion'; + +const testimonials = [ + { + quote: + "Bay Area Affiliates has been working on our office computers for years. They always go above and beyond to make sure we are back up and running smoothly. I wouldn't trust anyone but Elvin and Wallie to work on my computers. The absolute best in South Texas!", + initials: 'SG', + name: 'Stephanie Gomez', + role: 'Sales Representative, J & B Pavelka Truck and Trailer Sales', + layout: 'lg:col-span-5', + }, + { + quote: + "We cannot say enough great things about the service Wallie and Elvin at Bay Area Affiliates, Inc. have provided to our firm for well over 15 years! They take care of all of our computer/networking/software/website needs so we don't have to worry about anything related to IT. Whenever we have any computer/software issues, need to add new equipment, install software/upgrades etc., they are always there for us and respond quickly to keep our system running safely and securely. But besides their solid technical skills and vast computer systems knowledge, they are an absolute pleasure to work with. I recommend them to all of my clients and friends. They are the BEST of the BEST!", + initials: 'CB', + name: 'Cheryl Brown', + role: 'Owner & CPA, Hampton, Brown & Associates, P.C.', + layout: 'lg:col-span-7', + }, + { + quote: + 'We have used Bay Area Affiliates for many years and could not be happier with the expertise and service we have received.', + initials: 'GC', + name: 'George Craig', + role: 'President, SCI Security', + layout: 'lg:col-span-6 lg:col-start-4', + }, +]; + +const Testimonials: React.FC = () => { + return ( +
+
+
+ + Client Testimonials + +

+ Trusted by businesses who need IT that actually shows up +

+
+ +
+ {testimonials.map((testimonial, index) => ( + +
+
+ format_quote +
+ +
+ {[1, 2, 3, 4, 5].map((star) => ( + star + ))} +
+ +
+ "{testimonial.quote}" +
+ +
+
+ {testimonial.initials} +
+
+
{testimonial.name}
+
{testimonial.role}
+
+
+ + ))} +
+
+
+ ); +}; + +export default Testimonials; diff --git a/docker-compose.yml b/docker-compose.yml index 4770b86..4056e2f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,5 +5,11 @@ services: dockerfile: Dockerfile container_name: bayarea-site restart: unless-stopped + environment: + AMAZON_USER: ${AMAZON_USER:-} + AMAZON_PASSWORD: ${AMAZON_PASSWORD:-} + CONTACT_TO_EMAIL: ${CONTACT_TO_EMAIL:-info@bayareaaffiliates.com} + CONTACT_FROM_EMAIL: ${CONTACT_FROM_EMAIL:-info@bayareaaffiliates.com} + PORT: 8080 ports: - - "127.0.0.1:8080:80" + - "127.0.0.1:8080:8080" diff --git a/index.html b/index.html index 039a42f..9db38ee 100644 --- a/index.html +++ b/index.html @@ -12,7 +12,7 @@ - +
diff --git a/package-lock.json b/package-lock.json index c212302..8541ebd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,10 @@ "version": "0.0.0", "dependencies": { "@studio-freight/lenis": "^1.0.42", + "express": "^5.2.1", "framer-motion": "^12.26.2", "gsap": "^3.14.2", + "nodemailer": "^8.0.5", "react": "^19.2.3", "react-dom": "^19.2.3", "react-router-dom": "^7.12.0" @@ -1842,6 +1844,19 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -1943,6 +1958,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -1990,6 +2029,44 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -2069,6 +2146,28 @@ "node": ">= 6" } }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -2089,6 +2188,15 @@ "url": "https://opencollective.com/express" } }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -2106,7 +2214,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2120,6 +2227,15 @@ } } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -2144,6 +2260,26 @@ "dev": true, "license": "MIT" }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, "node_modules/electron-to-chromium": { "version": "1.5.267", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", @@ -2151,6 +2287,45 @@ "dev": true, "license": "ISC" }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", @@ -2203,6 +2378,73 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -2274,6 +2516,36 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fraction.js": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", @@ -2315,6 +2587,15 @@ } } }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2334,7 +2615,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2350,6 +2630,43 @@ "node": ">=6.9.0" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-tsconfig": { "version": "4.13.0", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", @@ -2376,17 +2693,40 @@ "node": ">=10.13.0" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gsap": { "version": "3.14.2", "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.14.2.tgz", "integrity": "sha512-P8/mMxVLU7o4+55+1TCnQrPmgjPKnwkzkXOK1asnR9Jg2lna4tEY5qBJjMmAaOBDDZWtlRjBXjLa0w53G/uBLA==", "license": "Standard 'no charge' license: https://gsap.com/standard-license." }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -2395,6 +2735,57 @@ "node": ">= 0.4" } }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -2457,6 +2848,12 @@ "node": ">=0.12.0" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/jiti": { "version": "1.21.7", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", @@ -2530,6 +2927,36 @@ "yallist": "^3.0.2" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2567,6 +2994,31 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/mini-svg-data-uri": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", @@ -2596,7 +3048,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/mz": { @@ -2630,6 +3081,15 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/node-releases": { "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", @@ -2637,6 +3097,15 @@ "dev": true, "license": "MIT" }, + "node_modules/nodemailer": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.5.tgz", + "integrity": "sha512-0PF8Yb1yZuQfQbq+5/pZJrtF6WQcjTd5/S4JOHs9PGFxuTqoB/icwuB44pOdURHJbRKX1PPoJZtY7R4VUoCC8w==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -2667,6 +3136,48 @@ "node": ">= 6" } }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -2674,6 +3185,16 @@ "dev": true, "license": "MIT" }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -2870,6 +3391,34 @@ "dev": true, "license": "MIT" }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -2891,6 +3440,30 @@ ], "license": "MIT" }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/react": { "version": "19.2.3", "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", @@ -3083,6 +3656,22 @@ "fsevents": "~2.3.2" } }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -3107,6 +3696,12 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -3123,12 +3718,63 @@ "semver": "bin/semver.js" } }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/set-cookie-parser": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", "license": "MIT" }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, "node_modules/sharp": { "version": "0.34.5", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", @@ -3187,6 +3833,78 @@ "node": ">=10" } }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3197,6 +3915,15 @@ "node": ">=0.10.0" } }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/sucrase": { "version": "3.35.1", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", @@ -3324,6 +4051,15 @@ "node": ">=8.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -3841,6 +4577,20 @@ "@esbuild/win32-x64": "0.27.2" } }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -3862,6 +4612,15 @@ "dev": true, "license": "MIT" }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -3900,6 +4659,15 @@ "dev": true, "license": "MIT" }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/vite": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", @@ -3975,6 +4743,12 @@ } } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/package.json b/package.json index acb5e22..fd81954 100644 --- a/package.json +++ b/package.json @@ -2,19 +2,23 @@ "name": "bay-area-affiliates", "private": true, "version": "0.0.0", - "type": "module", + "type": "module", "scripts": { "dev": "vite", + "dev:api": "node --watch --env-file=.env server.mjs --api-only --port=3013", "build": "tsx scripts/generate-sitemap.ts && tsx scripts/generate-robots.ts && vite build && node scripts/prune-dist-assets.mjs && tsx scripts/prerender-routes.ts", + "start": "node server.mjs", "preview": "vite preview", - "generate:seo": "tsx scripts/generate-sitemap.ts && tsx scripts/generate-robots.ts", - "optimize:images": "node scripts/optimize-images.mjs", - "prerender:routes": "tsx scripts/prerender-routes.ts" - }, + "generate:seo": "tsx scripts/generate-sitemap.ts && tsx scripts/generate-robots.ts", + "optimize:images": "node scripts/optimize-images.mjs", + "prerender:routes": "tsx scripts/prerender-routes.ts" + }, "dependencies": { "@studio-freight/lenis": "^1.0.42", + "express": "^5.2.1", "framer-motion": "^12.26.2", "gsap": "^3.14.2", + "nodemailer": "^8.0.5", "react": "^19.2.3", "react-dom": "^19.2.3", "react-router-dom": "^7.12.0" diff --git a/public/assets/services/business-email-services.webp b/public/assets/services/business-email-services.webp new file mode 100644 index 0000000..8fb7f73 Binary files /dev/null and b/public/assets/services/business-email-services.webp differ diff --git a/public/images/blog/business-email-comparison-new.webp b/public/images/blog/business-email-comparison-new.webp new file mode 100644 index 0000000..9c04c89 Binary files /dev/null and b/public/images/blog/business-email-comparison-new.webp differ diff --git a/public/sitemap.xml b/public/sitemap.xml index 3f77a96..0303a1d 100644 --- a/public/sitemap.xml +++ b/public/sitemap.xml @@ -2,133 +2,163 @@ https://bayareait.services - 2026-03-25 + 2026-04-20 monthly 1.0 + + https://bayareait.services/locations + 2026-04-20 + monthly + 0.8 + https://bayareait.services/services - 2026-03-25 + 2026-04-20 monthly 0.8 https://bayareait.services/blog - 2026-03-25 + 2026-04-20 monthly 0.8 https://bayareait.services/contact - 2026-03-25 + 2026-04-20 monthly 0.8 https://bayareait.services/about - 2026-03-25 + 2026-04-20 + monthly + 0.8 + + + https://bayareait.services/privacy-policy + 2026-04-20 + monthly + 0.8 + + + https://bayareait.services/terms-of-service + 2026-04-20 monthly 0.8 https://bayareait.services/locations/it-support-corpus-christi - 2026-03-25 + 2026-04-20 weekly 0.9 https://bayareait.services/locations/it-support-portland-tx - 2026-03-25 + 2026-04-20 weekly 0.9 https://bayareait.services/locations/it-support-rockport-tx - 2026-03-25 + 2026-04-20 weekly 0.9 https://bayareait.services/locations/it-support-aransas-pass-tx - 2026-03-25 + 2026-04-20 weekly 0.9 https://bayareait.services/locations/it-support-kingsville-tx - 2026-03-25 + 2026-04-20 weekly 0.9 https://bayareait.services/services/web-design-corpus-christi - 2026-03-25 + 2026-04-20 weekly 0.9 https://bayareait.services/services/business-email-corpus-christi - 2026-03-25 + 2026-04-20 weekly 0.9 https://bayareait.services/services/it-help-desk - 2026-03-25 + 2026-04-20 weekly 0.9 https://bayareait.services/services/computer-support - 2026-03-25 + 2026-04-20 weekly 0.9 https://bayareait.services/services/business-email-services - 2026-03-25 + 2026-04-20 weekly 0.9 https://bayareait.services/services/domain-registration-dns-support - 2026-03-25 + 2026-04-20 + weekly + 0.9 + + + https://bayareait.services/services/printer-scanner-installation + 2026-04-20 + weekly + 0.9 + + + https://bayareait.services/services/shared-drive + 2026-04-20 + weekly + 0.9 + + + https://bayareait.services/services/network-infrastructure-support + 2026-04-20 weekly 0.9 https://bayareait.services/blog/it-support-small-business-corpus-christi - 2026-03-25 + 2026-04-20 monthly 0.7 https://bayareait.services/blog/outsourced-it-support-corpus-christi - 2026-03-25 + 2026-04-20 monthly 0.7 https://bayareait.services/blog/it-service-vs-inhouse-it - 2026-03-25 + 2026-04-20 monthly 0.7 https://bayareait.services/blog/common-it-problems-businesses-corpus-christi - 2026-03-25 - monthly - 0.7 - - - https://bayareait.services/blog/it-support-cost-corpus-christi - 2026-03-25 + 2026-04-20 monthly 0.7 https://bayareait.services/blog/business-email-vs-google-workspace-vs-microsoft-365 - 2026-03-25 + 2026-04-20 monthly 0.7 diff --git a/scripts/prerender-routes.ts b/scripts/prerender-routes.ts index 6ed2a72..652c716 100644 --- a/scripts/prerender-routes.ts +++ b/scripts/prerender-routes.ts @@ -56,7 +56,7 @@ const staticRoutes: RouteMeta[] = [ route: '/blog', title: 'Blog | Bay Area IT Insights for Corpus Christi Businesses', description: - 'Read practical IT guidance for Corpus Christi and Coastal Bend businesses, from managed IT support and costs to business email and local service coverage.', + 'Read practical IT guidance for Corpus Christi and Coastal Bend businesses, from outsourced IT support to business email and local service coverage.', canonicalUrl: `${BASE_URL}/blog`, }, { diff --git a/server.mjs b/server.mjs new file mode 100644 index 0000000..5c39741 --- /dev/null +++ b/server.mjs @@ -0,0 +1,188 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import express from 'express'; +import nodemailer from 'nodemailer'; + +const DEFAULT_APP_PORT = 8080; +const DEFAULT_API_ONLY_PORT = 3013; +const DEFAULT_CONTACT_EMAIL = 'info@bayareaaffiliates.com'; +const SMTP_HOST = 'email-smtp.us-east-2.amazonaws.com'; +const SMTP_PORT = 587; +const REQUIRED_FIELDS = ['name', 'email', 'message']; +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const distDir = path.join(__dirname, 'dist'); + +function getFlag(name) { + return process.argv.includes(name); +} + +function getOption(name) { + const entry = process.argv.find((arg) => arg.startsWith(`${name}=`)); + return entry ? entry.slice(name.length + 1) : undefined; +} + +function getPort(apiOnly) { + const portValue = getOption('--port') || process.env.PORT || String(apiOnly ? DEFAULT_API_ONLY_PORT : DEFAULT_APP_PORT); + const port = Number.parseInt(portValue, 10); + + if (Number.isNaN(port)) { + throw new Error(`Invalid port value: ${portValue}`); + } + + return port; +} + +function trimField(value) { + return typeof value === 'string' ? value.trim() : ''; +} + +function sanitizeHeaderValue(value) { + return value.replace(/[\r\n]+/g, ' ').trim(); +} + +function validatePayload(payload) { + if (!payload || typeof payload !== 'object') { + return { ok: false, error: 'Invalid request payload.' }; + } + + const data = { + name: trimField(payload.name).slice(0, 120), + email: trimField(payload.email).slice(0, 160), + phone: trimField(payload.phone).slice(0, 80), + company: trimField(payload.company).slice(0, 120), + message: trimField(payload.message).slice(0, 4000), + website: trimField(payload.website).slice(0, 160), + }; + + for (const field of REQUIRED_FIELDS) { + if (!data[field]) { + return { ok: false, error: 'Please fill out all required fields.' }; + } + } + + if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) { + return { ok: false, error: 'Please provide a valid email address.' }; + } + + return { ok: true, data }; +} + +function createTransporter() { + const user = process.env.AMAZON_USER; + const pass = process.env.AMAZON_PASSWORD; + + if (!user || !pass) { + return null; + } + + return nodemailer.createTransport({ + host: SMTP_HOST, + secure: false, + port: SMTP_PORT, + auth: { + user, + pass, + }, + }); +} + +function buildMessageBody(data) { + return [ + 'New website inquiry', + '', + `Name: ${data.name}`, + `Email: ${data.email}`, + `Phone: ${data.phone || 'Not provided'}`, + `Company: ${data.company || 'Not provided'}`, + '', + 'Message:', + data.message, + ].join('\n'); +} + +function resolveRouteIndex(requestPath) { + const relativePath = requestPath.replace(/^\/+/, ''); + + if (!relativePath) { + return path.join(distDir, 'index.html'); + } + + return path.join(distDir, relativePath, 'index.html'); +} + +const apiOnly = getFlag('--api-only'); +const port = getPort(apiOnly); +const contactToEmail = process.env.CONTACT_TO_EMAIL || DEFAULT_CONTACT_EMAIL; +const contactFromEmail = process.env.CONTACT_FROM_EMAIL || contactToEmail; +const app = express(); + +app.disable('x-powered-by'); +app.use(express.json({ limit: '32kb' })); + +app.post('/api/contact', async (req, res) => { + const validation = validatePayload(req.body); + + if (!validation.ok) { + return res.status(400).json({ ok: false, error: validation.error }); + } + + const { data } = validation; + + if (data.website) { + return res.status(400).json({ ok: false, error: 'Invalid submission.' }); + } + + const transporter = createTransporter(); + + if (!transporter) { + return res.status(503).json({ + ok: false, + error: 'Email service is not configured right now. Please email us directly.', + }); + } + + try { + await transporter.sendMail({ + from: contactFromEmail, + to: contactToEmail, + replyTo: data.email, + subject: sanitizeHeaderValue(`Website inquiry from ${data.name}`), + text: buildMessageBody(data), + }); + + return res.json({ ok: true }); + } catch (error) { + console.error('Contact form delivery failed.', error); + + return res.status(500).json({ + ok: false, + error: 'Unable to send your message right now. Please call or email us directly.', + }); + } +}); + +if (!apiOnly) { + app.use(express.static(distDir, { index: 'index.html', redirect: false })); + + app.get(/^(?!\/api\/).*/, (req, res) => { + const routeIndex = resolveRouteIndex(req.path); + const fallbackIndex = path.join(distDir, 'index.html'); + + if (fs.existsSync(routeIndex)) { + return res.sendFile(routeIndex); + } + + return res.sendFile(fallbackIndex); + }); +} + +app.listen(port, () => { + if (!process.env.AMAZON_USER || !process.env.AMAZON_PASSWORD) { + console.warn('Amazon SES SMTP credentials are missing. /api/contact will return 503 until configured.'); + } + + console.log(`Bay Area IT server listening on port ${port}${apiOnly ? ' (API only)' : ''}.`); +}); diff --git a/src/data/seoData.ts b/src/data/seoData.ts index 4bcaddf..0ce182d 100644 --- a/src/data/seoData.ts +++ b/src/data/seoData.ts @@ -49,9 +49,9 @@ export const locationData: LocationData[] = [

Many local businesses choose outsourced IT support to gain access to professional expertise without the cost of an internal IT department.

Why Local Businesses Choose Bay Area IT

-

We have been serving Corpus Christi businesses for over 25 years — long before IT support became a crowded market. That local experience and longevity matters when you need a partner you can rely on.

+

We have been serving Corpus Christi businesses for over 30 years — long before IT support became a crowded market. That local experience and longevity matters when you need a partner you can rely on.

    -
  • 25+ years in the Coastal Bend — Deep roots in the local business community
  • +
  • 30+ years in the Coastal Bend — Deep roots in the local business community
  • 30+ local businesses supported — Hands-on experience across a range of industries
  • Fast response time — Quick remote support when something breaks, not a slow ticket queue
  • No jargon, no hidden fees — We explain what we do and charge what we quote
  • @@ -305,9 +305,9 @@ export const locationData: LocationData[] = [

    As your local IT partner, we also support your website long-term alongside your other technology needs. Learn more about our broader IT support services in Corpus Christi.

    A Local Partner for Your Online Presence

    -

    We are not a remote agency. We are a Corpus Christi IT company that has been working with local businesses for over 25 years — and we build websites that fit the local market because we live and work in it.

    +

    We are not a remote agency. We are a Corpus Christi IT company that has been working with local businesses for over 30 years — and we build websites that fit the local market because we live and work in it.

      -
    • 25+ years local — Deep experience with Coastal Bend businesses
    • +
    • 30+ years local — Deep experience with Coastal Bend businesses
    • Full-service — Web design, domain, email, and IT support under one roof
    • 30+ local clients — We understand what local businesses need to grow online
    • Transparent pricing — No hidden costs, no surprise invoices
    • @@ -368,10 +368,10 @@ export const locationData: LocationData[] = [

      Domain + Email, Handled Together

      A business email address only works correctly when the underlying domain and DNS are set up properly. We handle both — domain registration, DNS configuration, and email setup — as a combined service. Learn more about our business email services or our domain and DNS support.

      -

      A Local IT Team With 25 Years of Experience

      -

      Email problems are urgent. As a Corpus Christi IT provider with over 25 years in the Coastal Bend, we respond quickly and can be on-site when needed — no waiting in a remote support queue.

      +

      A Local IT Team With 30 Years of Experience

      +

      Email problems are urgent. As a Corpus Christi IT provider with over 30 years in the Coastal Bend, we respond quickly and can be on-site when needed — no waiting in a remote support queue.

        -
      • 25+ years in Corpus Christi — Long-term local presence, not a new provider
      • +
      • 30+ years in Corpus Christi — Long-term local presence, not a new provider
      • Fast response time — Quick remote support for email access issues
      • 30+ local businesses supported — Proven experience with companies like yours
      • Clear, honest communication — No hidden costs, no confusing technical language
      • @@ -427,11 +427,11 @@ export const serviceData: ServiceData[] = [

        Our help desk team in Corpus Christi ensures your employees stay productive by resolving technical problems quickly and efficiently.

        For broader support, consider our Computer Support or IT Support in Corpus Christi.

        -

        A Help Desk Backed by 25 Years of Local Experience

        -

        The value of a help desk is fast, clear support from people who know what they are doing. Our team has been helping Corpus Christi businesses since 2000 — we have seen it all and we respond quickly.

        +

        A Help Desk Backed by 30 Years of Local Experience

        +

        The value of a help desk is fast, clear support from people who know what they are doing. Our team has been helping Corpus Christi businesses since 1996 — we have seen it all and we respond quickly.

        • Fast response time — Employees get help when they need it, not hours later
        • -
        • 25+ years of IT experience — No problem is new to us
        • +
        • 30+ years of IT experience — No problem is new to us
        • 30+ local businesses supported — Across the Coastal Bend
        • No jargon — We communicate clearly with employees at every level
        @@ -534,9 +534,9 @@ export const serviceData: ServiceData[] = [ id: "business-email", slug: "services/business-email-services", title: "Business Email Services | Bay Area IT", - description: "Professional business email setup and support for small businesses in Corpus Christi. Domain-based addresses, Microsoft 365, and Google Workspace setup.", + description: "Professional business email setup and support for small businesses in Corpus Christi. Domain-based addresses, Microsoft Outlook setup, and iPhone and iPad configuration.", h1: "Business Email Services for Small Businesses", - keywords: ["business email services", "business email setup", "professional email address", "Microsoft 365 setup", "Google Workspace setup", "domain email"], + keywords: ["business email services", "business email setup", "professional email address", "Microsoft Outlook setup", "iPhone and iPad email setup", "domain email"], content: `

        Using a free Gmail or Yahoo address for business communication looks unprofessional and can hurt client trust. A domain-based email address — like yourname@yourbusiness.com — signals legitimacy and helps you stand out.

        We set up and support professional business email for small businesses in the Corpus Christi area, from initial configuration to ongoing maintenance and troubleshooting.

        @@ -544,8 +544,8 @@ export const serviceData: ServiceData[] = [

        What We Set Up

        Our business email services include:

          -
        • Microsoft 365 (Outlook) business email setup and licensing
        • -
        • Google Workspace (Gmail for Business) setup and configuration
        • +
        • Microsoft Outlook business email setup and licensing
        • +
        • iPhone and iPad setup and configuration
        • Domain-based email addresses (info@, contact@, yourname@)
        • Email DNS configuration — MX, SPF, DKIM, and DMARC records
        • Spam filter configuration and email security hardening
        • @@ -561,9 +561,9 @@ export const serviceData: ServiceData[] = [

          Whether you are switching from a free account or upgrading from a shared hosting email, we handle the transition with minimal disruption to your business.

          Why Businesses Trust Us With Their Email Setup

          -

          Email is critical infrastructure. A misconfigured setup means missed messages, spam complaints, or a security incident. With 25+ years of IT experience behind us, we get it right the first time.

          +

          Email is critical infrastructure. A misconfigured setup means missed messages, spam complaints, or a security incident. With 30+ years of IT experience behind us, we get it right the first time.

            -
          • 25+ years of IT experience — We have seen every email configuration problem that exists
          • +
          • 30+ years of IT experience — We have seen every email configuration problem that exists
          • Clear, jargon-free communication — We explain SPF, DKIM, and DMARC in plain language
          • Fast response — Based in Corpus Christi, not an overseas support queue
          • 30+ local businesses supported — Proven track record with Coastal Bend companies
          • @@ -576,8 +576,8 @@ export const serviceData: ServiceData[] = [ answer: "A domain-based email like john@yourbusiness.com — not a free Gmail or Yahoo account. It signals credibility, improves deliverability, and lets you control your communications." }, { - question: "Which platform do you recommend — Microsoft 365 or Google Workspace?", - answer: "Both are solid. Microsoft 365 is ideal if you rely on Word, Excel, and Teams. Google Workspace suits businesses already using Google tools. We help you choose based on your workflow and set it up either way." + question: "Do you set up Outlook on iPhone and iPad?", + answer: "Yes. We configure Microsoft Outlook on iPhone and iPad, connect it to your business email account, and make sure mail, calendars, and contacts sync correctly." }, { question: "Why are my business emails going to spam?", @@ -589,7 +589,7 @@ export const serviceData: ServiceData[] = [ }, { question: "How many email addresses can we create?", - answer: "That depends on your licensing plan. Most Microsoft 365 and Google Workspace plans allow one address per licensed user, plus unlimited aliases at no extra cost." + answer: "That depends on your licensing plan. Most business email plans allow one address per licensed user, plus aliases when needed for roles like info@ or support@." }, { question: "Do you offer ongoing support after the initial setup?", @@ -597,8 +597,8 @@ export const serviceData: ServiceData[] = [ } ] }, - { - id: "domain-dns", + { + id: "domain-dns", slug: "services/domain-registration-dns-support", title: "Domain Registration & DNS Support | Bay Area IT", description: "Domain registration, DNS setup, and email DNS configuration for small businesses. MX, SPF, DKIM, and DMARC records configured correctly from day one.", @@ -638,9 +638,9 @@ export const serviceData: ServiceData[] = [

            Ready to set up your business email on a proper domain? See our business email services for the full picture.

            Technical Expertise, Plain-Language Communication

            -

            DNS gets over-complicated fast. We handle the technical side — and explain what we are doing in plain language so you always know what is happening with your domain. 25+ years of hands-on experience means we have seen every DNS scenario.

            +

            DNS gets over-complicated fast. We handle the technical side — and explain what we are doing in plain language so you always know what is happening with your domain. 30+ years of hands-on experience means we have seen every DNS scenario.

              -
            • 25+ years of IT and DNS experience — Every configuration scenario covered
            • +
            • 30+ years of IT and DNS experience — Every configuration scenario covered
            • No jargon — MX records and DMARC explained in terms that make sense
            • Fast response — Local team in Corpus Christi, reachable when it is urgent
            @@ -670,10 +670,190 @@ export const serviceData: ServiceData[] = [ { question: "Can you manage our DNS on an ongoing basis?", answer: "Yes. We can manage your DNS as part of a support plan, handling changes, new records, and troubleshooting when services stop working." - } - ] - } -]; + } + ] + }, + { + id: "printer-scanner-installation", + slug: "services/printer-scanner-installation", + title: "Printer & Scanner Installation | Bay Area IT", + description: "Business printer and scanner installation in Corpus Christi. Network setup, scan-to-email, driver rollout, and reliable office printing support.", + h1: "Printer & Scanner Installation in Corpus Christi", + keywords: ["printer installation Corpus Christi", "scanner setup", "business printer support", "scan to email setup", "office printer configuration"], + content: ` +

            Printers and scanners should not be the reason work slows down. We set up office printing and scanning so your team can print, scan, and share documents without constant troubleshooting.

            +

            From a single front-desk printer to a multi-user office setup, we make sure devices are configured correctly for your network and users.

            + +

            What We Set Up

            +
              +
            • Network printer installation and driver deployment
            • +
            • Scanner setup including scan-to-email and scan-to-folder workflows
            • +
            • User access and workstation configuration
            • +
            • Troubleshooting for connectivity and printing errors
            • +
            • Best-practice placement and basic print reliability checks
            • +
            + +

            Why This Service Matters

            +

            Printer issues waste time because they interrupt simple daily tasks. A proper setup reduces recurring support tickets, user confusion, and document handling delays.

            +

            If your team also needs help with computers or file access, see our computer support services and shared drive setup.

            + +

            Configured for Real Office Workflows

            +

            We do not just plug in hardware and leave. We test the actual workflow your team uses, whether that means front-desk printing, shared-office scanning, or sending documents directly to email folders.

            +
              +
            • Reliable setup — Correct drivers, permissions, and network visibility
            • +
            • Clear user experience — Staff know where to print and scan without guesswork
            • +
            • Fast support — Local team available when printer issues return
            • +
            + `, + relatedServices: [4, 6, 7], + faq: [ + { + question: "Do you install business printers and scanners on-site?", + answer: "Yes. We can handle on-site installation, network configuration, and testing for office printers and scanners." + }, + { + question: "Can you set up scan-to-email or scan-to-folder?", + answer: "Yes. We configure scan workflows so documents can be sent to email inboxes, shared folders, or the correct users." + }, + { + question: "Do you help if the printer is already installed but keeps disconnecting?", + answer: "Yes. We troubleshoot recurring printer issues including driver problems, network visibility, permissions, and device configuration." + }, + { + question: "Can you connect multiple workstations to the same printer?", + answer: "Yes. We set up shared access for multiple users and make sure the device is available consistently across the office." + }, + { + question: "Do you support scanner setup for small offices?", + answer: "Yes. We support standalone scanners, multifunction printers, and common small-office document workflows." + }, + { + question: "Can this be included with ongoing IT support?", + answer: "Yes. Printer and scanner support can be handled as part of a broader monthly IT support arrangement." + } + ] + }, + { + id: "shared-drive", + slug: "services/shared-drive", + title: "Shared Drive Setup & Support | Bay Area IT", + description: "Shared drive setup for businesses in Corpus Christi. Folder structure, permissions, access support, and dependable file sharing for your team.", + h1: "Shared Drive Setup & Support in Corpus Christi", + keywords: ["shared drive setup", "file sharing support", "network drive setup", "business shared folders", "shared drive permissions"], + content: ` +

            A shared drive gives your team one reliable place to store, access, and organize business files. We set up shared storage so employees can work from the right files without version confusion or access problems.

            +

            Whether you need a simple office file share or a more structured shared folder system, we configure it to match how your team actually works.

            + +

            What We Configure

            +
              +
            • Shared drive or shared folder setup
            • +
            • Folder structure planning by department or role
            • +
            • User and group permissions
            • +
            • Mapped drive access on employee workstations
            • +
            • Basic backup and recovery alignment
            • +
            + +

            Why Shared File Access Breaks So Often

            +

            Most shared-drive issues come from unclear permissions, inconsistent folder structure, or machines that were never mapped properly. We fix the underlying setup so file access becomes predictable.

            +

            If you also need help with the network or office computers connected to those files, see our network infrastructure support and computer support.

            + +

            Built for Team Access, Not Guesswork

            +

            We keep shared storage practical: the right people can reach the right files, new employees can be added quickly, and your team is not left wondering where documents belong.

            +
              +
            • Organized access — Folder structure that reflects your business workflow
            • +
            • Permission control — Users only see what they should see
            • +
            • Long-term maintainability — Easy to update as your team changes
            • +
            + `, + relatedServices: [5, 4, 7], + faq: [ + { + question: "What is a shared drive for a business?", + answer: "It is a central file location your team can access for shared documents, forms, spreadsheets, and day-to-day business files." + }, + { + question: "Can you create folder permissions for different employees or departments?", + answer: "Yes. We can structure access by person, team, or department so sensitive files stay limited to the right users." + }, + { + question: "Can shared drives work for remote users too?", + answer: "Yes. Depending on your setup, we can support secure remote access and make sure off-site staff can reach the files they need." + }, + { + question: "Do you help migrate files into a new shared drive?", + answer: "Yes. We can assist with organizing and moving files into a cleaner shared structure." + }, + { + question: "Can you fix permission errors or missing mapped drives?", + answer: "Yes. Those are common issues, and we troubleshoot both access rights and workstation-side configuration." + }, + { + question: "Do shared drives need backups too?", + answer: "Yes. Shared storage should always be paired with a backup plan so files can be recovered if deleted, corrupted, or encrypted by malware." + } + ] + }, + { + id: "network-infrastructure-support", + slug: "services/network-infrastructure-support", + title: "Network Infrastructure Support | Bay Area IT", + description: "Business network support in Corpus Christi. Routers, switches, Wi-Fi, access points, segmentation, and practical network troubleshooting.", + h1: "Network Infrastructure Support in Corpus Christi", + keywords: ["network support Corpus Christi", "business wifi setup", "router and switch support", "office network troubleshooting", "network infrastructure support"], + content: ` +

            Your network is the foundation behind email, file access, cloud apps, printers, phones, and day-to-day productivity. When the network is unstable, everything else feels slow or unreliable.

            +

            We help businesses set up and maintain practical network infrastructure that supports stable connectivity, clear troubleshooting, and secure access.

            + +

            What We Support

            +
              +
            • Router, firewall, and switch configuration
            • +
            • Wi-Fi and access point setup
            • +
            • Basic network segmentation and device organization
            • +
            • Troubleshooting for slow or unstable connections
            • +
            • Network cleanup for growing offices and mixed device environments
            • +
            + +

            When Businesses Need Network Help

            +

            Most businesses call us when users complain that "the internet is slow," printers disappear, shared folders drop out, or coverage is inconsistent in parts of the office. We trace the issue to the network layer and clean it up properly.

            +

            Need help with the systems running on top of that network? See our shared drive support and IT help desk services.

            + +

            Designed for Stability and Supportability

            +

            A good network should be easy to maintain, not mysterious. We focus on practical improvements that reduce outages, improve coverage, and make future support simpler.

            +
              +
            • Better reliability — Fewer mystery disconnects and weak spots
            • +
            • Clearer structure — Equipment and traffic organized sensibly
            • +
            • Local support — Fast troubleshooting when the office depends on connectivity
            • +
            + `, + relatedServices: [6, 4, 7], + faq: [ + { + question: "What does network infrastructure support include?", + answer: "Typically routers, switches, Wi-Fi access points, firewall basics, connectivity troubleshooting, and improving how office devices connect." + }, + { + question: "Can you help with slow office Wi-Fi?", + answer: "Yes. We diagnose weak coverage, poor placement, interference, and equipment issues that commonly cause slow or unstable Wi-Fi." + }, + { + question: "Do you support both wired and wireless networks?", + answer: "Yes. We work with both wired office networks and wireless environments, depending on your setup." + }, + { + question: "Can you set up new access points or switches?", + answer: "Yes. We can install and configure core network hardware for small and mid-sized office environments." + }, + { + question: "Can you troubleshoot printers, file shares, and other devices on the network?", + answer: "Yes. Many device issues are actually network issues, and we troubleshoot those connections as part of the service." + }, + { + question: "Do you offer ongoing support after the initial setup?", + answer: "Yes. Network support can be provided as one-time project work or as part of ongoing IT support." + } + ] + } +]; export interface BlogPostData { id: string; @@ -688,7 +868,7 @@ export interface BlogPostData { redirect?: string; } -export const blogPostData: BlogPostData[] = [ +export const blogPostData: BlogPostData[] = [ { id: "small-business-it-problems", slug: "blog/it-support-small-business-corpus-christi", @@ -860,10 +1040,11 @@ export const blogPostData: BlogPostData[] = [ id: "it-support-cost", slug: "blog/it-support-cost-corpus-christi", title: "What Does IT Support Cost for Businesses in Corpus Christi?", - description: "Understand IT support pricing for businesses in Corpus Christi and why monthly managed services often cost less than reactive IT help.", - h1: "What Does IT Support Cost for Businesses in Corpus Christi?", - keywords: ["it support cost", "it support services", "outsourced it support", "it service near me"], - category: "authority", + description: "Understand IT support pricing for businesses in Corpus Christi and why monthly managed services often cost less than reactive IT help.", + h1: "What Does IT Support Cost for Businesses in Corpus Christi?", + keywords: ["it support cost", "it support services", "outsourced it support", "it service near me"], + category: "authority", + redirect: "/locations/it-support-corpus-christi", image: "/images/blog/it-support-cost.webp", content: `

            One of the most common questions businesses ask is how much IT support actually costs. The answer depends on several factors, but understanding pricing models helps you make informed decisions.

            @@ -1432,62 +1613,91 @@ export const blogPostData: BlogPostData[] = [ id: "business-email-comparison", slug: "blog/business-email-vs-google-workspace-vs-microsoft-365", title: "Business Email Compared: Free Gmail vs Google Workspace vs Microsoft 365", - description: "A practical comparison of free Gmail, Google Workspace, and Microsoft 365 for small businesses. Which email platform is right for your team?", + description: "A practical small-business guide to choosing between free email, Google Workspace, and Microsoft 365, including setup, migration, and deliverability considerations.", h1: "Business Email Compared: Free Gmail vs Google Workspace vs Microsoft 365", keywords: ["business email comparison", "Google Workspace vs Microsoft 365", "business email for small business", "professional email address"], category: "authority", - image: "/images/blog/business-email-comparison-new.png", - content: ` - -

            If you are running a small business and still using a free Gmail or Yahoo account for client communication, you are not alone — but it is costing you credibility. The question most business owners have is: what should I switch to?

            -

            Here is a straightforward comparison of your three main options.

            - -

            Option 1: Free Gmail or Yahoo (Not Recommended for Business)

            -

            Free consumer email accounts are tempting because they cost nothing, but they come with real business drawbacks:

            -
              -
            • No custom domain — Your address is name@gmail.com, not name@yourbusiness.com
            • -
            • Lower trust — Clients and vendors notice, and it signals a lack of professionalism
            • -
            • No admin control — You cannot manage employee accounts, reset passwords, or audit access
            • -
            • Deliverability risks — Free accounts have weaker authentication, increasing spam likelihood
            • -
            • No business continuity — If an employee leaves, you may lose access to that inbox
            • -
            -

            For a solo freelancer just starting out, a free account is acceptable. For any established business, it sends the wrong signal.

            - -

            Option 2: Google Workspace (Gmail for Business)

            -

            Google Workspace gives you Gmail with your own domain name, plus the full Google productivity suite.

            -
              -
            • Familiar interface — If you already use Gmail, the learning curve is nearly zero
            • -
            • Included tools — Google Drive, Docs, Sheets, Meet, Calendar, and Chat
            • -
            • Strong mobile apps — Works seamlessly on Android and iPhone
            • -
            • Admin console — Add and remove users, manage devices, set security policies
            • -
            • Starting price — Business Starter plan begins around $6/user/month
            • -
            -

            Best for: Teams that prefer a browser-based workflow, already use Google tools, or work heavily on mobile.

            - -

            Option 3: Microsoft 365 Business (Outlook for Business)

            -

            Microsoft 365 is the standard in most professional and corporate environments, bundling Outlook with the full Office suite.

            -
              -
            • Outlook email — Powerful calendar, contacts, and task integration
            • -
            • Included apps — Word, Excel, PowerPoint, Teams, SharePoint, and OneDrive
            • -
            • Desktop apps included — Most plans include installed Office apps, not just browser versions
            • -
            • Strong compliance tools — Better fit for regulated industries (healthcare, finance, legal)
            • -
            • Starting price — Business Basic begins around $6/user/month; Business Standard around $12.50
            • -
            -

            Best for: Teams that rely on Excel, Word, or PowerPoint; businesses in regulated industries; offices that prefer desktop applications over browser tools.

            - -

            Which One Should You Choose?

            -

            For most small businesses in Corpus Christi, either Google Workspace or Microsoft 365 is a solid choice at a similar price point. The decision usually comes down to:

            -
              -
            • What tools your team already uses day-to-day
            • -
            • Whether you need desktop Office apps or browser-based work is fine
            • -
            • Whether any clients or vendors require compatibility with specific formats
            • -
            -

            What matters most is getting off free consumer email and onto a platform with a custom domain, proper authentication records, and admin control.

            - -

            The Setup Step Most Businesses Skip

            -

            Whichever platform you choose, correct DNS configuration is essential. SPF, DKIM, and DMARC records must be set up properly or your emails will land in spam — regardless of which platform you use.

            -

            Ready to get set up? See our business email services for how we handle the full setup — platform selection, DNS configuration, migration, and ongoing support.

            - ` - } -]; + image: "/images/blog/business-email-comparison-new.webp", + content: ` + +

            For a small business, email is more than messaging. It is your sales follow-up, customer communication, calendar, file sharing, and a big part of how professional your company looks. If you are deciding between free email, Google Workspace, and Microsoft 365, the real question is not just cost. It is ownership, reliability, and how well the platform fits the way your team works.

            + +

            Start Here: Free Email Is Fine for Personal Use, Not for Business

            +

            Free Gmail, Yahoo, and similar inboxes are easy to start with, but they create long-term problems for established businesses:

            +
              +
            • No branded domain - You are advertising gmail.com instead of your own company name
            • +
            • Weak account control - There is no proper admin workflow for employee turnover, security, or shared ownership
            • +
            • Deliverability issues - Business domains need SPF, DKIM, and DMARC configured correctly to stay out of spam folders
            • +
            • Poor continuity - Important client communication can stay tied to one person's personal inbox
            • +
            • Lower trust - Clients and vendors expect a professional address for a real business
            • +
            +

            If you have customers, employees, or recurring vendor communication, free email is usually the wrong long-term setup.

            + +

            Google Workspace: Best for Teams That Already Live in Gmail

            +

            Google Workspace is usually the easiest upgrade for businesses that already like Gmail and prefer browser-based tools.

            +
              +
            • Very low learning curve - Staff who already know Gmail adapt quickly
            • +
            • Strong collaboration tools - Drive, Docs, Sheets, Meet, and shared calendars are simple and fast
            • +
            • Good mobile experience - Works well for teams using iPhones, Android phones, and web apps
            • +
            • Simple admin controls - You can add users, suspend accounts, enforce MFA, and manage devices
            • +
            • Clean fit for smaller teams - Especially good when the company wants lightweight, cloud-first workflows
            • +
            +

            Choose Google Workspace if: your team likes Gmail, collaborates heavily in browser tools, and does not depend on desktop Office files all day.

            + +

            Microsoft 365: Best for Teams Built Around Outlook and Office

            +

            Microsoft 365 is usually the better choice when the business already depends on Outlook, Excel, Word, Teams, and traditional office workflows.

            +
              +
            • Strong Outlook environment - Familiar for many office teams and admin-heavy workflows
            • +
            • Desktop Office apps - Important when staff live in Excel, Word, and PowerPoint
            • +
            • Broader business controls - Useful for permissions, device management, and more structured environments
            • +
            • Good fit for regulated businesses - Often preferred in healthcare, finance, legal, and other documentation-heavy teams
            • +
            • Integrated Microsoft ecosystem - Teams, OneDrive, SharePoint, and Office all work together
            • +
            +

            Choose Microsoft 365 if: your team uses Outlook and Office heavily, exchanges Office documents constantly, or wants a more traditional office software stack.

            + +

            Quick Decision Guide

            +

            For most small businesses in Corpus Christi, the platform decision comes down to a few practical questions:

            +
              +
            • Already happy with Gmail? Google Workspace is usually the cleanest move
            • +
            • Already dependent on Outlook, Excel, or Word? Microsoft 365 is usually the safer fit
            • +
            • Need the fewest moving parts for a small team? Google Workspace often feels simpler
            • +
            • Need more formal desktop-app workflows and tighter Microsoft integration? Microsoft 365 usually wins
            • +
            +

            For most businesses, the biggest mistake is not choosing the wrong paid platform. It is staying too long on a free setup with no real admin ownership, no clean migration plan, and no deliverability protection.

            + +

            The Setup Work Matters More Than Most Businesses Expect

            +

            Platform choice is only part of the job. The actual business risk usually appears during setup and migration:

            +
              +
            • domain verification and DNS changes
            • +
            • SPF, DKIM, and DMARC configuration
            • +
            • mailbox migration from old providers
            • +
            • phone, tablet, and desktop app setup
            • +
            • shared mailbox, alias, and forwarding decisions
            • +
            • user onboarding and lockout prevention
            • +
            +

            If those steps are handled poorly, the result is missed mail, spam-folder problems, sync issues, or employees who cannot access the right inboxes on day one.

            + +

            What We Recommend for Small Businesses

            +

            For most established small businesses, the right move is simple: get off free email, choose the platform your team will actually use, and set it up correctly the first time. That usually means Google Workspace or Microsoft 365 with a branded domain, proper security records, and a clean migration path.

            + +

            If you want help choosing the right platform and getting everything configured correctly, see our business email services. We handle platform selection, DNS setup, migration, device setup, and ongoing support for businesses in Corpus Christi and the Coastal Bend.

            + ` + } +]; + +const blogDisplayPriority = [ + "business-email-comparison", + "small-business-it-problems", + "common-it-problems", + "it-service-vs-inhouse", + "when-to-outsource-it", +]; + +export const orderedBlogPostData = [...blogPostData].sort((left, right) => { + const leftPriority = blogDisplayPriority.indexOf(left.id); + const rightPriority = blogDisplayPriority.indexOf(right.id); + const safeLeft = leftPriority === -1 ? Number.MAX_SAFE_INTEGER : leftPriority; + const safeRight = rightPriority === -1 ? Number.MAX_SAFE_INTEGER : rightPriority; + return safeLeft - safeRight; +}); diff --git a/src/hooks/useContactForm.ts b/src/hooks/useContactForm.ts new file mode 100644 index 0000000..5b1ccdf --- /dev/null +++ b/src/hooks/useContactForm.ts @@ -0,0 +1,81 @@ +import { useState } from 'react'; + +type ContactFormStatus = 'idle' | 'submitting' | 'success' | 'error'; + +type ContactApiResponse = { + ok?: boolean; + error?: string; +}; + +const DEFAULT_ERROR_MESSAGE = 'Unable to send your message right now. Please try again or email us directly.'; + +function getValue(formData: FormData, key: string) { + return String(formData.get(key) || '').trim(); +} + +export function useContactForm() { + const [status, setStatus] = useState('idle'); + const [errorMessage, setErrorMessage] = useState(''); + + const resetFeedback = () => { + if (status === 'idle') { + return; + } + + setStatus('idle'); + setErrorMessage(''); + }; + + const submitContactForm = async (event: React.FormEvent) => { + event.preventDefault(); + + if (status === 'submitting') { + return; + } + + const form = event.currentTarget; + const formData = new FormData(form); + const payload = { + name: getValue(formData, 'name'), + email: getValue(formData, 'email'), + phone: getValue(formData, 'phone'), + company: getValue(formData, 'company'), + message: getValue(formData, 'message'), + website: getValue(formData, 'website'), + }; + + setStatus('submitting'); + setErrorMessage(''); + + try { + const response = await fetch('/api/contact', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }); + const data = (await response.json().catch(() => null)) as ContactApiResponse | null; + + if (!response.ok || !data?.ok) { + throw new Error(data?.error || DEFAULT_ERROR_MESSAGE); + } + + form.reset(); + setStatus('success'); + } catch (error) { + setStatus('error'); + setErrorMessage(error instanceof Error ? error.message : DEFAULT_ERROR_MESSAGE); + } + }; + + return { + status, + errorMessage, + isSubmitting: status === 'submitting', + isSubmitted: status === 'success', + hasError: status === 'error', + resetFeedback, + submitContactForm, + }; +} diff --git a/src/index.css b/src/index.css index b12eeb5..9283d4e 100644 --- a/src/index.css +++ b/src/index.css @@ -8,6 +8,16 @@ overflow-y: auto; } + ::selection { + background: #000; + color: #fff; + } + + ::-moz-selection { + background: #000; + color: #fff; + } + body { background-color: #0a0a0a; color: white; @@ -18,6 +28,16 @@ } @layer utilities { + .selection-inverse::selection { + background: #000; + color: #fff; + } + + .selection-inverse::-moz-selection { + background: #000; + color: #fff; + } + .no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; diff --git a/src/pages/AboutPage.tsx b/src/pages/AboutPage.tsx index 9da5b29..ca00eac 100644 --- a/src/pages/AboutPage.tsx +++ b/src/pages/AboutPage.tsx @@ -60,7 +60,7 @@ const AboutPage: React.FC = () => { ]; const timeline = [ - { year: '2000', title: 'Founded in Corpus Christi', desc: 'Started with a mission to bring enterprise-level IT solutions to local businesses.' }, + { year: '1996', title: 'Founded in Corpus Christi', desc: 'Started with a mission to bring enterprise-level IT solutions to local businesses.' }, { year: '2015', title: 'Expanded Service Portfolio', desc: 'Added cloud services and advanced networking to serve growing businesses.' }, { year: '2020', title: 'Remote Work Transformation', desc: 'Helped local businesses strengthen remote access, security, and day-to-day support during a disruptive period.' }, { year: '2024', title: 'Leading the Coastal Bend', desc: 'Now supporting 30+ local businesses with practical, reliable IT infrastructure.' }, @@ -70,7 +70,7 @@ const AboutPage: React.FC = () => { <> @@ -93,7 +93,7 @@ const AboutPage: React.FC = () => { transition={{ delay: 0.1 }} className="text-xl text-gray-600 dark:text-gray-300 max-w-3xl mx-auto leading-relaxed" > - Since 2000, we've been helping businesses in Corpus Christi and surrounding communities build reliable, secure technology foundations that keep work moving. + Since 1996, we've been helping businesses in Corpus Christi and surrounding communities build reliable, secure technology foundations that keep work moving.
@@ -124,7 +124,7 @@ const AboutPage: React.FC = () => { {[ { label: 'Businesses served', value: 30, suffix: '+' }, { label: 'Uptime achieved', value: 99.9, suffix: '%' }, - { label: 'Years of service', value: 25, suffix: '+' }, + { label: 'Years of service', value: 30, suffix: '+' }, { label: 'Response time', value: 2, prefix: '<', suffix: 'min' }, ].map((stat, index) => (
diff --git a/src/pages/BlogPage.tsx b/src/pages/BlogPage.tsx index 263a09a..f92a5f3 100644 --- a/src/pages/BlogPage.tsx +++ b/src/pages/BlogPage.tsx @@ -3,7 +3,7 @@ import { motion } from 'framer-motion'; import { Link } from 'react-router-dom'; import Contact from '../../components/Contact'; import SEO from '../../components/SEO'; -import { blogPostData } from '../data/seoData'; +import { orderedBlogPostData } from '../data/seoData'; const cardVariants = { hidden: { opacity: 0, y: 20 }, @@ -27,7 +27,7 @@ const BlogPage: React.FC = () => { <> @@ -45,7 +45,7 @@ const BlogPage: React.FC = () => {
- {blogPostData.filter(post => !post.redirect).map((post, index) => ( + {orderedBlogPostData.filter(post => !post.redirect).map((post, index) => ( { className="group grid md:grid-cols-2 gap-0 bg-white dark:bg-[#161616] rounded-3xl overflow-hidden shadow-lg border border-gray-100 dark:border-white/5 hover:shadow-2xl hover:shadow-blue-900/10 transition-all duration-300" >
- {post.h1} diff --git a/src/pages/ContactPage.tsx b/src/pages/ContactPage.tsx index ca76d73..3785db6 100644 --- a/src/pages/ContactPage.tsx +++ b/src/pages/ContactPage.tsx @@ -1,49 +1,23 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { motion } from 'framer-motion'; + import SEO from '../../components/SEO'; - +import { useContactForm } from '../hooks/useContactForm'; + const ContactPage: React.FC = () => { - const [isSubmitted, setIsSubmitted] = useState(false); + const { errorMessage, hasError, isSubmitted, isSubmitting, resetFeedback, submitContactForm } = useContactForm(); useEffect(() => { window.scrollTo(0, 0); }, []); - const handleSubmit = (event: React.FormEvent) => { - event.preventDefault(); + const faqs = [ + { q: 'How quickly can you start?', a: 'Most assessments can begin within 48 hours of contact. Emergency support is available 24/7.' }, + { q: 'How do you price services?', a: 'Transparent monthly pricing based on devices and services needed. No hidden fees or surprise charges.' }, + { q: 'What\'s included in support?', a: '24/7 monitoring, helpdesk, proactive maintenance, security updates, and SLA guarantees.' }, + ]; - const formData = new FormData(event.currentTarget); - const name = String(formData.get('name') || '').trim(); - const phone = String(formData.get('phone') || '').trim(); - const email = String(formData.get('email') || '').trim(); - const company = String(formData.get('company') || '').trim(); - const message = String(formData.get('message') || '').trim(); - - const subject = encodeURIComponent(`Website inquiry from ${name || 'Bay Area IT visitor'}`); - const body = encodeURIComponent( - [ - `Name: ${name || 'Not provided'}`, - `Phone: ${phone || 'Not provided'}`, - `Email: ${email || 'Not provided'}`, - `Company: ${company || 'Not provided'}`, - '', - 'Message:', - message || 'No message provided', - ].join('\n'), - ); - - window.location.href = `mailto:info@bayareaaffiliates.com?subject=${subject}&body=${body}`; - setIsSubmitted(true); - event.currentTarget.reset(); - }; - - const faqs = [ - { q: 'How quickly can you start?', a: 'Most assessments can begin within 48 hours of contact. Emergency support is available 24/7.' }, - { q: 'How do you price services?', a: 'Transparent monthly pricing based on devices and services needed. No hidden fees or surprise charges.' }, - { q: 'What\'s included in support?', a: '24/7 monitoring, helpdesk, proactive maintenance, security updates, and SLA guarantees.' }, - ]; - - return ( + return ( <> { canonicalUrl="https://bayareait.services/contact" />
-
-
- {/* Hero */} -
-

- Let's talk about
your IT needs -

-

- Ready to improve your technology? We're here to help. Get started with a free consultation and see how we can make your IT work better for you. -

-
- -
-
- - {/* Left: Contact Form */} - -

Send us a message

-
-
-
- +
+
+ {/* Hero */} +
+

+ Let's talk about
your IT needs +

+

+ Ready to improve your technology? We're here to help. Get started with a free consultation and see how we can make your IT work better for you. +

+
+ +
+
+ + {/* Left: Contact Form */} + +

Send us a message

+ + +
+
+ -
-
- +
+
+ -
-
-
- +
+
+
+ -
-
- +
+
+ -
-
- +
+
+
- {isSubmitted && (

- Your mail client should open with the message prefilled. If it does not, email us at info@bayareaaffiliates.com. + Thanks. Your message was sent successfully. If you need immediate help, email us at info@bayareaaffiliates.com. +

+ )} + {hasError && ( +

+ {errorMessage}

)} - - - {/* Right: FAQ & Info */} - - {/* Contact Info */} -
-
-
- call -
-

Phone

-

(361) 765-8400

-
-
-
- location_on -
-

Address

-

1001 Blucher St,
Corpus Christi, TX 78401

-
-
- -
-

Hours & Area

-

Mon - Fri: 8:00 AM - 6:00 PM

-

(Emergency support: 24/7)

-

Service Area: Corpus Christi & the Coastal Bend (including Portland, Rockport, Aransas Pass, Kingsville, Port Aransas)

-
- - {/* FAQ */} -
-

Quick Answers

-
- {faqs.map((faq, index) => ( -
-

{faq.q}

-

{faq.a}

-
- ))} -
-
-
-
-
- - {/* What Happens Next Section */} -
-
-

- What happens next? -

-
- {[ - { - step: "01", - title: "We respond quickly", - description: "Get a response within 24 hours, usually much faster." - }, - { - step: "02", - title: "Free consultation", - description: "20-minute call to understand your needs and challenges." - }, - { - step: "03", - title: "Custom proposal", - description: "Tailored solution with clear next steps and pricing." - } - ].map((item, index) => ( - -
- {item.step} -
-
-
- - {index === 0 ? 'schedule_send' : index === 1 ? 'phone_in_talk' : 'contract_edit'} - -
-

{item.title}

-

{item.description}

-
-
- ))} -
-
-
- - {/* Service Area Section */} -
-
-
-
-
-

- Our Service Area -

-

- Proudly serving Corpus Christi, Portland, Rockport, Aransas Pass, Kingsville, Port Aransas, and the entire Coastal Bend region. -

- -
- {['Corpus Christi', 'Portland', 'Rockport', 'Aransas Pass', 'Kingsville', 'Port Aransas'].map((city) => ( -
- location_on - {city} -
- ))} -
-
- -
- -
-
-
-
+ + + {/* Right: FAQ & Info */} + + {/* Contact Info */} +
+
+
+ call +
+

Phone

+

(361) 765-8400

+
+
+
+ location_on +
+

Address

+

1001 Blucher St,
Corpus Christi, TX 78401

+
+
+ +
+

Hours & Area

+

Mon - Fri: 8:00 AM - 6:00 PM

+

(Emergency support: 24/7)

+

Service Area: Corpus Christi & the Coastal Bend (including Portland, Rockport, Aransas Pass, Kingsville, Port Aransas)

+
+ + {/* FAQ */} +
+

Quick Answers

+
+ {faqs.map((faq, index) => ( +
+

{faq.q}

+

{faq.a}

+
+ ))} +
+
+
+
+
+ + {/* What Happens Next Section */} +
+
+

+ What happens next? +

+
+ {[ + { + step: '01', + title: 'We respond quickly', + description: 'Get a response within 24 hours, usually much faster.' + }, + { + step: '02', + title: 'Free consultation', + description: '20-minute call to understand your needs and challenges.' + }, + { + step: '03', + title: 'Custom proposal', + description: 'Tailored solution with clear next steps and pricing.' + } + ].map((item, index) => ( + +
+ {item.step} +
+
+
+ + {index === 0 ? 'schedule_send' : index === 1 ? 'phone_in_talk' : 'contract_edit'} + +
+

{item.title}

+

{item.description}

+
+
+ ))} +
+
+
+ + {/* Service Area Section */} +
+
+
+
+
+

+ Our Service Area +

+

+ Proudly serving Corpus Christi, Portland, Rockport, Aransas Pass, Kingsville, Port Aransas, and the entire Coastal Bend region. +

+ +
+ {['Corpus Christi', 'Portland', 'Rockport', 'Aransas Pass', 'Kingsville', 'Port Aransas'].map((city) => ( +
+ location_on + {city} +
+ ))} +
+
+ +
+ +
+
+
+
); }; - -export default ContactPage; + +export default ContactPage; diff --git a/src/pages/LocationPage.tsx b/src/pages/LocationPage.tsx index ce5caac..fe8bc73 100644 --- a/src/pages/LocationPage.tsx +++ b/src/pages/LocationPage.tsx @@ -112,11 +112,11 @@ const LocationPage: React.FC = ({ data }) => {
{/* Hero Section */} -
+
{/* Parallax Background */}
@@ -168,14 +168,14 @@ const LocationPage: React.FC = ({ data }) => {

- - Get IT Support - + + Get IT Support + = ({ data }) => {
{/* Main Content Section */} -
-
-
+
+
+
= ({ data }) => { whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ duration: 0.6 }} - className="bg-white/80 dark:bg-white/5 backdrop-blur-xl rounded-3xl p-12 md:p-16 shadow-2xl border border-gray-100 dark:border-white/10" - > + className="bg-white/80 dark:bg-white/5 backdrop-blur-xl rounded-3xl p-8 md:p-10 lg:p-12 shadow-2xl border border-gray-100 dark:border-white/10" + >
diff --git a/src/pages/LocationsPage.tsx b/src/pages/LocationsPage.tsx index 9b8defa..59b72a1 100644 --- a/src/pages/LocationsPage.tsx +++ b/src/pages/LocationsPage.tsx @@ -83,7 +83,7 @@ const LocationsPage: React.FC = () => { Based in Corpus Christi. Serving the Region.

- Our team is locally based with over 25 years supporting Coastal Bend businesses. + Our team is locally based with over 30 years supporting Coastal Bend businesses. Most issues are resolved remotely for speed. On-site support is available across all cities we serve.

diff --git a/src/pages/NotFoundPage.tsx b/src/pages/NotFoundPage.tsx new file mode 100644 index 0000000..fc952c9 --- /dev/null +++ b/src/pages/NotFoundPage.tsx @@ -0,0 +1,216 @@ +import React, { useEffect } from 'react'; +import { motion } from 'framer-motion'; +import { Link, useLocation } from 'react-router-dom'; +import SEO from '../../components/SEO'; + +const routeCards = [ + { + title: 'Business Email Services', + description: 'Enterprise cloud email, migration, DNS setup and local support for Corpus Christi businesses.', + to: '/services/business-email-services', + icon: 'mail', + }, + { + title: 'Service Areas', + description: 'Browse all Coastal Bend locations we support, from Corpus Christi to surrounding cities.', + to: '/locations', + icon: 'location_on', + }, + { + title: 'Contact Bay Area IT', + description: 'Talk to the team directly if you were expecting a live page or need help now.', + to: '/contact', + icon: 'support_agent', + }, +]; + +const quickLinks = [ + { label: 'Home', to: '/' }, + { label: 'Services', to: '/services' }, + { label: 'Locations', to: '/locations' }, + { label: 'Blog', to: '/blog' }, +]; + +const diagnostics = [ + 'Check whether an old bookmark or outdated link was used.', + 'Some content has been moved to new service or location URLs.', + 'If you still need this URL, contact the team directly.', +]; + +const NotFoundPage: React.FC = () => { + const location = useLocation(); + + useEffect(() => { + window.scrollTo(0, 0); + }, []); + + const canonicalUrl = + typeof window !== 'undefined' + ? `${window.location.origin}${location.pathname}` + : 'https://bayareait.services/404'; + + return ( + <> + + +
+
+
+
+
+
+ +
+
+ + + + Route Offline + + +

+ 404 +

+ +

+ This page is no longer on the network. +

+ +

+ The requested path does not exist, has been moved, or is now live under a new URL. + Use the direct links below to quickly get back to services, locations, or the contact page. +

+ +
+ + Back to Home + + + View Email Service + +
+ +
+ {quickLinks.map((item) => ( + + {item.label} + + ))} +
+
+ + +
+
+

Diagnostic

+

Requested Path

+
+
+ Bay Area IT logo +
+
+ +
+

Path

+

{location.pathname}

+
+ +
+ {diagnostics.map((item) => ( +
+ check_circle +

{item}

+
+ ))} +
+ +
+

Direct Help

+

+ Call (361) 765-8400 or open the contact page if you need support. +

+ + Open Contact Page + arrow_outward + +
+
+
+
+ +
+
+
+ +

Recommended Entry Points

+
+ +
+ {routeCards.map((card, index) => ( + + +
+ {card.icon} +
+

{card.title}

+

{card.description}

+ + Open Page + + arrow_forward + + + +
+ ))} +
+
+
+
+ + ); +}; + +export default NotFoundPage; diff --git a/src/pages/ServicePage.tsx b/src/pages/ServicePage.tsx index b9c6502..2958ec4 100644 --- a/src/pages/ServicePage.tsx +++ b/src/pages/ServicePage.tsx @@ -121,7 +121,7 @@ const ServicePage: React.FC = ({ data }) => {
{/* Parallax Background */}
@@ -176,8 +176,8 @@ const ServicePage: React.FC = ({ data }) => {
Get Started @@ -195,9 +195,9 @@ const ServicePage: React.FC = ({ data }) => {
{/* Main Content Section */} -
+
-
+
= ({ data }) => { whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ duration: 0.6 }} - className="bg-white/80 dark:bg-white/5 backdrop-blur-xl rounded-3xl p-12 md:p-16 shadow-2xl border border-gray-100 dark:border-white/10" + className="bg-white/80 dark:bg-white/5 backdrop-blur-xl rounded-3xl p-8 md:p-10 lg:p-12 shadow-2xl border border-gray-100 dark:border-white/10" >
diff --git a/src/pages/ServicesPage.tsx b/src/pages/ServicesPage.tsx index fb99806..f114bf4 100644 --- a/src/pages/ServicesPage.tsx +++ b/src/pages/ServicesPage.tsx @@ -5,7 +5,7 @@ import SEO from '../../components/SEO'; const services = [ { - id: 1, + id: 2, title: 'Bay Area Email Services', description: 'Our flagship service: enterprise cloud email with 99.99% uptime, premium deliverability, local Corpus Christi support, and 25 GB mailboxes for $5 per inbox.', challenge: 'Missed emails, spam-folder delivery, and server downtime quietly cost businesses leads, approvals, and customer trust.', @@ -23,26 +23,46 @@ const services = [ 'Current email provider details' ], icon: 'mail', - image: '/assets/services/business-it.webp' + image: '/assets/services/business-email-services.webp' }, { - id: 2, - title: 'Shared Drive', - description: 'Setup and management of shared drive solutions so your team can store, access, and organize files reliably.', - challenge: 'Data growth requires scalable storage that is accessible yet secure from loss.', - approach: 'We deploy shared drive solutions that centralize your files and make them easy for your team to access securely.', + id: 7, + title: 'IT Help Desk', + description: 'Fast and reliable help desk support for employees, resolving technical issues remotely or on-site.', + challenge: 'When employees do not have a clear support channel, small technical issues quickly turn into company-wide delays.', + approach: 'We provide a structured help desk workflow that resolves day-to-day IT issues quickly and keeps your team productive.', deliverables: [ - 'Shared drive setup and configuration', - 'Folder structure and permissions', - 'User permission management', - 'Remote access configuration', - 'Backup integration' + 'Remote troubleshooting and issue resolution', + 'User account and access support', + 'Software and email assistance', + 'Escalation path for on-site issues', + 'Clear communication on ticket status' ], needs: [ - 'Capacity requirements', - 'Access patterns' + 'Primary support contacts', + 'User and device overview' ], - icon: 'storage', + icon: 'support_agent', + image: '' + }, + { + id: 4, + title: 'New/Refurbished Desktop Hardware', + description: 'Supply and installation of new or refurbished desktop hardware, tailored to meet your business requirements.', + challenge: 'Sourcing the right hardware at the right price can be time-consuming and risky.', + approach: 'We source high-quality new and refurbished equipment that meets your specs and budget, fully tested and ready to go.', + deliverables: [ + 'Hardware procurement', + 'Quality assurance testing', + 'Image deployment', + 'Peripherals setup', + 'Warranty management' + ], + needs: [ + 'Budget constraints', + 'Performance requirements' + ], + icon: 'computer', image: '' }, { @@ -66,7 +86,7 @@ const services = [ image: '' }, { - id: 4, + id: 1, title: 'Web Design', description: 'Professional website design backed by domain registration and DNS support, so your online presence looks credible and works reliably.', challenge: 'If your site looks dated, loads poorly, or your domain setup is messy, customers lose trust before they ever contact you.', @@ -87,63 +107,43 @@ const services = [ image: '/assets/services/managed-it.webp' }, { - id: 5, - title: 'New/Refurbished Desktop Hardware', - description: 'Supply and installation of new or refurbished desktop hardware, tailored to meet your business requirements.', - challenge: 'Sourcing the right hardware at the right price can be time-consuming and risky.', - approach: 'We source high-quality new and refurbished equipment that meets your specs and budget, fully tested and ready to go.', + id: 6, + title: 'Shared Drive', + description: 'Setup and management of shared drive solutions so your team can store, access, and organize files reliably.', + challenge: 'Data growth requires scalable storage that is accessible yet secure from loss.', + approach: 'We deploy shared drive solutions that centralize your files and make them easy for your team to access securely.', deliverables: [ - 'Hardware procurement', - 'Quality assurance testing', - 'Image deployment', - 'Peripherals setup', - 'Warranty management' + 'Shared drive setup and configuration', + 'Folder structure and permissions', + 'User permission management', + 'Remote access configuration', + 'Backup integration' ], needs: [ - 'Budget constraints', - 'Performance requirements' + 'Capacity requirements', + 'Access patterns' ], - icon: 'computer', + icon: 'storage', image: '' }, { - id: 6, + id: 5, title: 'Network Infrastructure Support', description: 'Robust network solutions to ensure connectivity, security, and efficiency, including routers, access points, and switches.', challenge: 'A weak network backbone leads to slow speeds, dropped calls, and security holes.', approach: 'We design and maintain enterprise-grade networks that handle your data traffic reliably and securely.', - deliverables: [ - 'Router & Switch configuration', - 'VLAN segmentation', - 'Wi-Fi optimization', - 'Network monitoring setup', - 'Cable management' - ], - needs: [ - 'Floor plans (for Wi-Fi)', - 'ISP details' - ], - icon: 'lan', - image: '' - }, - { - id: 7, - title: 'IT Help Desk', - description: 'Fast and reliable help desk support for employees, resolving technical issues remotely or on-site.', - challenge: 'When employees do not have a clear support channel, small technical issues quickly turn into company-wide delays.', - approach: 'We provide a structured help desk workflow that resolves day-to-day IT issues quickly and keeps your team productive.', deliverables: [ - 'Remote troubleshooting and issue resolution', - 'User account and access support', - 'Software and email assistance', - 'Escalation path for on-site issues', - 'Clear communication on ticket status' + 'Router & Switch configuration', + 'VLAN segmentation', + 'Wi-Fi optimization', + 'Network monitoring setup', + 'Cable management' ], needs: [ - 'Primary support contacts', - 'User and device overview' + 'Floor plans (for Wi-Fi)', + 'ISP details' ], - icon: 'support_agent', + icon: 'lan', image: '' } ]; @@ -199,16 +199,19 @@ const ServiceModal: React.FC<{ service: typeof services[0] | null; onClose: () = {/* Hero Image in Modal */} - {service.image && ( -
- {service.title} -
-
- )} + {service.image && ( +
+ {service.title} +
+
+ )}
diff --git a/src/routes/seoRoutes.tsx b/src/routes/seoRoutes.tsx index f9b540d..b0bc3a6 100644 --- a/src/routes/seoRoutes.tsx +++ b/src/routes/seoRoutes.tsx @@ -102,6 +102,9 @@ export const serviceRoutes = [ { path: '/services/computer-support', slug: 'services/computer-support' }, { path: '/services/business-email-services', slug: 'services/business-email-services' }, { path: '/services/domain-registration-dns-support', slug: 'services/domain-registration-dns-support' }, + { path: '/services/printer-scanner-installation', slug: 'services/printer-scanner-installation' }, + { path: '/services/shared-drive', slug: 'services/shared-drive' }, + { path: '/services/network-infrastructure-support', slug: 'services/network-infrastructure-support' }, ]; // Authority blog posts only — location posts redirect to their location pages @@ -110,7 +113,6 @@ export const blogRoutes = [ { path: '/blog/outsourced-it-support-corpus-christi', slug: 'blog/outsourced-it-support-corpus-christi' }, { path: '/blog/it-service-vs-inhouse-it', slug: 'blog/it-service-vs-inhouse-it' }, { path: '/blog/common-it-problems-businesses-corpus-christi', slug: 'blog/common-it-problems-businesses-corpus-christi' }, - { path: '/blog/it-support-cost-corpus-christi', slug: 'blog/it-support-cost-corpus-christi' }, { path: '/blog/business-email-vs-google-workspace-vs-microsoft-365', slug: 'blog/business-email-vs-google-workspace-vs-microsoft-365' }, ]; @@ -125,8 +127,12 @@ export const legacyRedirects = [ { from: '/computer-support', to: '/services/computer-support' }, { from: '/business-email-services', to: '/services/business-email-services' }, { from: '/domain-registration-dns-support', to: '/services/domain-registration-dns-support' }, + { from: '/printer-scanner-installation', to: '/services/printer-scanner-installation' }, + { from: '/shared-drive', to: '/services/shared-drive' }, + { from: '/network-infrastructure-support', to: '/services/network-infrastructure-support' }, { from: '/web-design-corpus-christi', to: '/services/web-design-corpus-christi' }, { from: '/business-email-corpus-christi', to: '/services/business-email-corpus-christi' }, + { from: '/blog/it-support-cost-corpus-christi', to: '/locations/it-support-corpus-christi' }, // Location blog posts → their canonical location pages { from: '/blog/it-support-corpus-christi-blog', to: '/locations/it-support-corpus-christi' }, { from: '/blog/it-support-portland-tx-blog', to: '/locations/it-support-portland-tx' }, diff --git a/vite.config.ts b/vite.config.ts index c091b0a..903aec9 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -4,12 +4,18 @@ import react from '@vitejs/plugin-react'; export default defineConfig(() => { return { - server: { - port: 3012, - host: '0.0.0.0', - hmr: { - clientPort: 3012, - }, + server: { + port: 3012, + host: '0.0.0.0', + proxy: { + '/api': { + target: 'http://127.0.0.1:3013', + changeOrigin: false, + }, + }, + hmr: { + clientPort: 3012, + }, watch: { usePolling: true, },