Issues git resolved
This commit is contained in:
5
.env.example
Normal file
5
.env.example
Normal file
@@ -0,0 +1,5 @@
|
||||
AMAZON_USER=
|
||||
AMAZON_PASSWORD=
|
||||
CONTACT_TO_EMAIL=info@bayareaaffiliates.com
|
||||
CONTACT_FROM_EMAIL=info@bayareaaffiliates.com
|
||||
PORT=8080
|
||||
11
.gitignore
vendored
11
.gitignore
vendored
@@ -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/*
|
||||
|
||||
3
App.tsx
3
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 = () => {
|
||||
<Route path={from} element={<Navigate to={to} replace />} />
|
||||
</React.Fragment>
|
||||
))}
|
||||
|
||||
<Route path="*" element={<NotFoundPage />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
</main>
|
||||
|
||||
15
Dockerfile
15
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"]
|
||||
|
||||
BIN
Modern_business_email_202604201852.png
Normal file
BIN
Modern_business_email_202604201852.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 MiB |
20
README.md
20
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.
|
||||
|
||||
@@ -25,16 +25,16 @@ const BackToTop: React.FC = () => {
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{isVisible && (
|
||||
<motion.button
|
||||
initial={{ opacity: 0, scale: 0.8, y: 20 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.8, y: 20 }}
|
||||
whileHover={{ scale: 1.1, backgroundColor: "#3b82f6" }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
onClick={scrollToTop}
|
||||
className="fixed bottom-8 right-8 z-50 w-12 h-12 flex items-center justify-center rounded-full bg-black dark:bg-white text-white dark:text-black shadow-lg border border-gray-700 dark:border-gray-200 transition-colors"
|
||||
aria-label="Back to top"
|
||||
>
|
||||
<motion.button
|
||||
initial={{ opacity: 0, scale: 0.8, y: 20 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.8, y: 20 }}
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
onClick={scrollToTop}
|
||||
className="selection-inverse fixed bottom-8 right-8 z-50 w-12 h-12 flex items-center justify-center rounded-full bg-black dark:bg-white text-white dark:text-black shadow-lg border border-gray-700 dark:border-gray-200 transition-colors"
|
||||
aria-label="Back to top"
|
||||
>
|
||||
<span className="material-symbols-outlined text-2xl">arrow_upward</span>
|
||||
</motion.button>
|
||||
)}
|
||||
@@ -42,4 +42,4 @@ const BackToTop: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default BackToTop;
|
||||
export default BackToTop;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
<Link
|
||||
to="/contact"
|
||||
className="px-8 py-4 bg-black dark:bg-white text-white dark:text-black rounded-full font-medium transition-all hover:scale-105 shadow-lg w-full sm:w-auto"
|
||||
>
|
||||
Book a 20-minute assessment
|
||||
</Link>
|
||||
<Link
|
||||
to="/contact"
|
||||
className="px-8 py-4 bg-gray-100 dark:bg-white/10 text-gray-900 dark:text-white rounded-full font-medium transition-all hover:bg-gray-200 dark:hover:bg-white/20 w-full sm:w-auto"
|
||||
>
|
||||
Send a message
|
||||
</Link>
|
||||
<Link
|
||||
to="/contact"
|
||||
className="selection-inverse px-8 py-4 bg-black dark:bg-white text-white dark:text-black rounded-full font-medium transition-all hover:scale-105 shadow-lg w-full sm:w-auto"
|
||||
>
|
||||
Book a 20-minute assessment
|
||||
</Link>
|
||||
<Link
|
||||
to="/contact"
|
||||
className="selection-inverse px-8 py-4 bg-gray-100 dark:bg-white/10 text-gray-900 dark:text-white rounded-full font-medium transition-all hover:bg-gray-200 dark:hover:bg-white/20 w-full sm:w-auto"
|
||||
>
|
||||
Send a message
|
||||
</Link>
|
||||
<a
|
||||
href="tel:+13617658400"
|
||||
className="text-sm text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors mt-2"
|
||||
|
||||
@@ -1,113 +1,136 @@
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
const Contact: React.FC = () => {
|
||||
return (
|
||||
<motion.section
|
||||
id="contact"
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: "-100px" }}
|
||||
transition={{ duration: 0.8, ease: "easeOut" }}
|
||||
className="py-24 bg-white dark:bg-[#0f0f0f] border-t border-gray-100 dark:border-white/5"
|
||||
>
|
||||
<div className="max-w-3xl mx-auto px-6">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<h2 className="font-display text-4xl md:text-5xl font-medium mb-6 text-gray-900 dark:text-white">
|
||||
Get in Touch
|
||||
</h2>
|
||||
<p className="text-gray-600 dark:text-gray-400 text-lg">
|
||||
We're here to help you with all your IT needs.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<motion.form
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="space-y-6"
|
||||
>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label htmlFor="name" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Name *</label>
|
||||
<motion.input
|
||||
whileFocus={{ scale: 1.01, borderColor: "#3b82f6" }}
|
||||
transition={{ duration: 0.2 }}
|
||||
type="text"
|
||||
id="name"
|
||||
placeholder="Your Name"
|
||||
required
|
||||
className="w-full px-4 py-3 rounded-lg bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-400/20 focus:border-blue-500 outline-none transition-all"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Email *</label>
|
||||
<motion.input
|
||||
whileFocus={{ scale: 1.01, borderColor: "#3b82f6" }}
|
||||
transition={{ duration: 0.2 }}
|
||||
type="email"
|
||||
id="email"
|
||||
placeholder="Your Email"
|
||||
required
|
||||
className="w-full px-4 py-3 rounded-lg bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-400/20 focus:border-blue-500 outline-none transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label htmlFor="phone" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Phone (optional)</label>
|
||||
<motion.input
|
||||
whileFocus={{ scale: 1.01, borderColor: "#3b82f6" }}
|
||||
transition={{ duration: 0.2 }}
|
||||
type="tel"
|
||||
id="phone"
|
||||
placeholder="Your Phone Number"
|
||||
className="w-full px-4 py-3 rounded-lg bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-400/20 focus:border-blue-500 outline-none transition-all"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="company" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Company (optional)</label>
|
||||
<motion.input
|
||||
whileFocus={{ scale: 1.01, borderColor: "#3b82f6" }}
|
||||
transition={{ duration: 0.2 }}
|
||||
type="text"
|
||||
id="company"
|
||||
placeholder="Your Company Name"
|
||||
className="w-full px-4 py-3 rounded-lg bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-400/20 focus:border-blue-500 outline-none transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="message" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Message *</label>
|
||||
<motion.textarea
|
||||
whileFocus={{ scale: 1.01, borderColor: "#3b82f6" }}
|
||||
transition={{ duration: 0.2 }}
|
||||
id="message"
|
||||
placeholder="Your Message"
|
||||
required
|
||||
className="w-full px-4 py-3 rounded-lg bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-400/20 focus:border-blue-500 outline-none transition-all h-32 resize-none"
|
||||
></motion.textarea>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<motion.button
|
||||
type="submit"
|
||||
whileHover={{ scale: 1.05, backgroundColor: "#3b82f6", color: "#ffffff", border: "none" }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="px-8 py-3 bg-black dark:bg-white text-white dark:text-black rounded-full font-medium transition-all duration-300 w-full md:w-auto shadow-lg"
|
||||
>
|
||||
Send Message
|
||||
</motion.button>
|
||||
</div>
|
||||
</motion.form>
|
||||
</div>
|
||||
</motion.section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Contact;
|
||||
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 (
|
||||
<motion.section
|
||||
id="contact"
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: "-100px" }}
|
||||
transition={{ duration: 0.8, ease: "easeOut" }}
|
||||
className="py-24 bg-white dark:bg-[#0f0f0f] border-t border-gray-100 dark:border-white/5"
|
||||
>
|
||||
<div className="max-w-3xl mx-auto px-6">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<h2 className="font-display text-4xl md:text-5xl font-medium mb-6 text-gray-900 dark:text-white">
|
||||
Get in Touch
|
||||
</h2>
|
||||
<p className="text-gray-600 dark:text-gray-400 text-lg">
|
||||
We're here to help you with all your IT needs.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<motion.form
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="space-y-6"
|
||||
onChange={resetFeedback}
|
||||
onSubmit={submitContactForm}
|
||||
>
|
||||
<input type="text" name="website" tabIndex={-1} autoComplete="off" className="hidden" aria-hidden="true" />
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label htmlFor="contact-name" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Name *</label>
|
||||
<motion.input
|
||||
whileFocus={{ scale: 1.01, borderColor: "#3b82f6" }}
|
||||
transition={{ duration: 0.2 }}
|
||||
type="text"
|
||||
id="contact-name"
|
||||
name="name"
|
||||
placeholder="Your Name"
|
||||
required
|
||||
className="w-full px-4 py-3 rounded-lg bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-400/20 focus:border-blue-500 outline-none transition-all"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="contact-email" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Email *</label>
|
||||
<motion.input
|
||||
whileFocus={{ scale: 1.01, borderColor: "#3b82f6" }}
|
||||
transition={{ duration: 0.2 }}
|
||||
type="email"
|
||||
id="contact-email"
|
||||
name="email"
|
||||
placeholder="Your Email"
|
||||
required
|
||||
className="w-full px-4 py-3 rounded-lg bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-400/20 focus:border-blue-500 outline-none transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label htmlFor="contact-phone" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Phone (optional)</label>
|
||||
<motion.input
|
||||
whileFocus={{ scale: 1.01, borderColor: "#3b82f6" }}
|
||||
transition={{ duration: 0.2 }}
|
||||
type="tel"
|
||||
id="contact-phone"
|
||||
name="phone"
|
||||
placeholder="Your Phone Number"
|
||||
className="w-full px-4 py-3 rounded-lg bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-400/20 focus:border-blue-500 outline-none transition-all"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="contact-company" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Company (optional)</label>
|
||||
<motion.input
|
||||
whileFocus={{ scale: 1.01, borderColor: "#3b82f6" }}
|
||||
transition={{ duration: 0.2 }}
|
||||
type="text"
|
||||
id="contact-company"
|
||||
name="company"
|
||||
placeholder="Your Company Name"
|
||||
className="w-full px-4 py-3 rounded-lg bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-400/20 focus:border-blue-500 outline-none transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="contact-message" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Message *</label>
|
||||
<motion.textarea
|
||||
whileFocus={{ scale: 1.01, borderColor: "#3b82f6" }}
|
||||
transition={{ duration: 0.2 }}
|
||||
id="contact-message"
|
||||
name="message"
|
||||
placeholder="Your Message"
|
||||
required
|
||||
className="w-full px-4 py-3 rounded-lg bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-400/20 focus:border-blue-500 outline-none transition-all h-32 resize-none"
|
||||
></motion.textarea>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<motion.button
|
||||
type="submit"
|
||||
whileHover={{ scale: isSubmitting ? 1 : 1.05 }}
|
||||
whileTap={{ scale: isSubmitting ? 1 : 0.95 }}
|
||||
disabled={isSubmitting}
|
||||
className="selection-inverse px-8 py-3 bg-black dark:bg-white text-white dark:text-black rounded-full font-medium transition-all duration-300 w-full md:w-auto shadow-lg disabled:cursor-not-allowed disabled:opacity-70"
|
||||
>
|
||||
{isSubmitting ? 'Sending...' : 'Send Message'}
|
||||
</motion.button>
|
||||
</div>
|
||||
{isSubmitted && (
|
||||
<p className="text-sm text-green-600 dark:text-green-400">
|
||||
Thanks. Your message was sent successfully.
|
||||
</p>
|
||||
)}
|
||||
{hasError && (
|
||||
<p className="text-sm text-red-600 dark:text-red-400">
|
||||
{errorMessage}
|
||||
</p>
|
||||
)}
|
||||
</motion.form>
|
||||
</div>
|
||||
</motion.section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Contact;
|
||||
|
||||
@@ -13,7 +13,7 @@ const Footer: React.FC = () => {
|
||||
<span className="font-display font-bold text-lg tracking-tight text-gray-900 dark:text-white">Bay Area IT</span>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 max-w-xs mb-6">
|
||||
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.
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-3 text-sm text-gray-600 dark:text-gray-400">
|
||||
<a href="tel:+13617658400" className="transition-colors hover:text-gray-900 dark:hover:text-white">
|
||||
@@ -29,11 +29,13 @@ const Footer: React.FC = () => {
|
||||
<h4 className="text-sm font-bold text-gray-900 dark:text-white mb-6 uppercase tracking-wider">Services</h4>
|
||||
<ul className="space-y-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
{[
|
||||
{ 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) => (
|
||||
<li key={item.label}>
|
||||
<motion.div whileHover={{ x: 5 }} className="inline-block">
|
||||
|
||||
@@ -110,13 +110,13 @@ const Hero: React.FC = () => {
|
||||
<div className="relative z-10 text-center max-w-4xl px-6">
|
||||
<div className="hero-stagger flex items-center justify-center gap-2 mb-6">
|
||||
<span className="h-px w-8 bg-gray-400 dark:bg-gray-500"></span>
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-gray-600 dark:text-gray-400 font-medium">Serving the Coastal Bend since 2000</span>
|
||||
<span className="text-xs uppercase tracking-[0.2em] text-gray-600 dark:text-gray-400 font-medium">Serving the Coastal Bend since 1996</span>
|
||||
<span className="h-px w-8 bg-gray-400 dark:bg-gray-500"></span>
|
||||
</div>
|
||||
|
||||
<h1 className="hero-stagger font-display text-5xl md:text-7xl lg:text-8xl font-medium tracking-tighter leading-[1.1] mb-8 text-gray-900 dark:text-white">
|
||||
Reliable IT Services<br />
|
||||
<span className="text-gray-500 dark:text-gray-500">for Over 25 Years</span>
|
||||
<span className="text-gray-500 dark:text-gray-500">for Over 30 Years</span>
|
||||
</h1>
|
||||
|
||||
<p className="hero-stagger text-lg md:text-xl text-gray-600 dark:text-gray-300 max-w-2xl mx-auto mb-10 font-light leading-relaxed">
|
||||
@@ -124,21 +124,21 @@ const Hero: React.FC = () => {
|
||||
</p>
|
||||
|
||||
<div className="hero-stagger flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||
<motion.a
|
||||
href="#services"
|
||||
className="px-8 py-3 bg-black dark:bg-white text-white dark:text-black rounded-full font-medium"
|
||||
whileHover={{ scale: 1.05, backgroundColor: "#3b82f6", color: "#ffffff" }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
IT Services
|
||||
</motion.a>
|
||||
<motion.a
|
||||
href="#contact"
|
||||
className="px-8 py-3 bg-transparent border border-gray-300 dark:border-white/20 text-gray-900 dark:text-white rounded-full font-medium"
|
||||
whileHover={{ scale: 1.05, backgroundColor: "rgba(255,255,255,0.1)", borderColor: "#ffffff" }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
Get in Touch
|
||||
<motion.a
|
||||
href="#services"
|
||||
className="selection-inverse px-8 py-3 bg-black dark:bg-white text-white dark:text-black rounded-full font-medium"
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
IT Services
|
||||
</motion.a>
|
||||
<motion.a
|
||||
href="/contact"
|
||||
className="px-8 py-3 bg-transparent border border-gray-300 dark:border-white/20 text-gray-900 dark:text-white rounded-full font-medium"
|
||||
whileHover={{ scale: 1.05, backgroundColor: "rgba(255,255,255,0.1)", borderColor: "#ffffff" }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
Get in Touch
|
||||
</motion.a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -34,12 +34,12 @@ const Navbar: React.FC = () => {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Link
|
||||
to="/contact"
|
||||
className="hidden md:inline-flex items-center px-4 py-1.5 bg-black dark:bg-white text-white dark:text-black rounded-full text-sm font-medium hover:bg-gray-800 dark:hover:bg-gray-100 transition-colors"
|
||||
>
|
||||
Get IT Support
|
||||
</Link>
|
||||
<Link
|
||||
to="/contact"
|
||||
className="selection-inverse hidden md:inline-flex items-center px-4 py-1.5 bg-black dark:bg-white text-white dark:text-black rounded-full text-sm font-medium hover:bg-gray-800 dark:hover:bg-gray-100 transition-colors"
|
||||
>
|
||||
Get IT Support
|
||||
</Link>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
|
||||
@@ -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<ServicesProps> = ({ preview = false, featuredIds }) =>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
{isRestrictedView && (
|
||||
<div className="mt-12 text-center">
|
||||
<button
|
||||
onClick={() => setShowAll(true)}
|
||||
className="inline-flex items-center gap-2 px-8 py-3 bg-black dark:bg-white text-white dark:text-black rounded-full font-medium hover:bg-gray-800 dark:hover:bg-gray-200 transition-colors"
|
||||
>
|
||||
Show More Services <span className="material-symbols-outlined text-sm">expand_more</span>
|
||||
</button>
|
||||
{isRestrictedView && (
|
||||
<div className="mt-12 text-center">
|
||||
<button
|
||||
onClick={() => setShowAll(true)}
|
||||
className="selection-inverse inline-flex items-center gap-2 px-8 py-3 bg-black dark:bg-white text-white dark:text-black rounded-full font-medium hover:bg-gray-800 dark:hover:bg-gray-200 transition-colors"
|
||||
>
|
||||
Show More Services <span className="material-symbols-outlined text-sm">expand_more</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,44 +1,86 @@
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
const Testimonials: React.FC = () => {
|
||||
return (
|
||||
<section className="py-24 px-6 bg-background-light dark:bg-background-dark relative overflow-hidden bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.05),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.05),rgba(255,255,255,0))]">
|
||||
<div className="max-w-5xl mx-auto">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
viewport={{ once: true }}
|
||||
className="bg-white dark:bg-white/5 backdrop-blur-sm p-8 md:p-12 rounded-3xl border border-gray-200 dark:border-white/10 shadow-2xl relative"
|
||||
>
|
||||
{/* Quote Icon */}
|
||||
<div className="absolute top-8 right-8 text-blue-100 dark:text-white/5 select-none">
|
||||
<span className="material-symbols-outlined text-8xl">format_quote</span>
|
||||
</div>
|
||||
|
||||
<div className="flex text-yellow-400 mb-6 gap-1 relative z-10">
|
||||
{[1, 2, 3, 4, 5].map((star) => (
|
||||
<span key={star} className="material-symbols-outlined fill-current">star</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<blockquote className="text-xl md:text-2xl font-medium leading-relaxed text-gray-900 dark:text-white mb-8 relative z-10">
|
||||
"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."
|
||||
</blockquote>
|
||||
|
||||
<div className="flex items-center gap-4 relative z-10">
|
||||
<div className="w-12 h-12 bg-black dark:bg-white rounded-full flex items-center justify-center text-white dark:text-black font-bold text-lg">
|
||||
SM
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-bold text-gray-900 dark:text-white">Sarah Martinez</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">Operations Manager, Coastal Medical Group</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
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 (
|
||||
<section className="py-24 px-6 bg-background-light dark:bg-background-dark relative overflow-hidden bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.05),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.05),rgba(255,255,255,0))]">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<div className="text-center mb-12">
|
||||
<span className="inline-flex items-center gap-2 px-4 py-2 rounded-full border border-gray-200 dark:border-white/10 bg-white/80 dark:bg-white/5 text-xs font-semibold uppercase tracking-[0.2em] text-gray-500 dark:text-gray-400">
|
||||
Client Testimonials
|
||||
</span>
|
||||
<h2 className="mt-5 font-display text-3xl md:text-4xl text-gray-900 dark:text-white">
|
||||
Trusted by businesses who need IT that <span className="text-gray-500 dark:text-gray-400">actually shows up</span>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-12">
|
||||
{testimonials.map((testimonial, index) => (
|
||||
<motion.div
|
||||
key={testimonial.name}
|
||||
initial={{ opacity: 0, y: 24 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: index * 0.1 }}
|
||||
className={`min-w-0 bg-white/90 dark:bg-white/5 backdrop-blur-sm p-8 rounded-3xl border border-gray-200 dark:border-white/10 shadow-[0_24px_80px_rgba(15,23,42,0.08)] dark:shadow-[0_24px_80px_rgba(0,0,0,0.28)] relative h-full overflow-hidden ${testimonial.layout}`}
|
||||
>
|
||||
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top_right,rgba(59,130,246,0.12),transparent_30%)] dark:bg-[radial-gradient(circle_at_top_right,rgba(255,255,255,0.08),transparent_30%)] pointer-events-none" />
|
||||
<div className="absolute top-6 right-6 text-blue-100 dark:text-white/5 select-none">
|
||||
<span className="material-symbols-outlined text-7xl">format_quote</span>
|
||||
</div>
|
||||
|
||||
<div className="flex text-yellow-400 mb-6 gap-1 relative z-10">
|
||||
{[1, 2, 3, 4, 5].map((star) => (
|
||||
<span key={star} className="material-symbols-outlined fill-current">star</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<blockquote className="text-base md:text-lg font-medium leading-relaxed text-gray-900 dark:text-white mb-8 relative z-10 break-words [overflow-wrap:anywhere]">
|
||||
"{testimonial.quote}"
|
||||
</blockquote>
|
||||
|
||||
<div className="flex items-center gap-4 relative z-10 mt-auto">
|
||||
<div className="w-12 h-12 shrink-0 bg-black dark:bg-white rounded-full flex items-center justify-center text-white dark:text-black font-bold text-lg">
|
||||
{testimonial.initials}
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="font-bold text-gray-900 dark:text-white">{testimonial.name}</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400 break-words [overflow-wrap:anywhere]">{testimonial.role}</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Testimonials;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet" />
|
||||
</head>
|
||||
<body class="bg-background-light dark:bg-background-dark text-gray-900 dark:text-white font-sans antialiased selection:bg-white selection:text-black transition-colors duration-300">
|
||||
<body class="bg-background-light dark:bg-background-dark text-gray-900 dark:text-white font-sans antialiased selection:bg-black selection:text-white transition-colors duration-300">
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/index.tsx"></script>
|
||||
</body>
|
||||
|
||||
782
package-lock.json
generated
782
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
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"
|
||||
|
||||
BIN
public/assets/services/business-email-services.webp
Normal file
BIN
public/assets/services/business-email-services.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
BIN
public/images/blog/business-email-comparison-new.webp
Normal file
BIN
public/images/blog/business-email-comparison-new.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
@@ -2,133 +2,163 @@
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://bayareait.services</loc>
|
||||
<lastmod>2026-03-25</lastmod>
|
||||
<lastmod>2026-04-20</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bayareait.services/locations</loc>
|
||||
<lastmod>2026-04-20</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bayareait.services/services</loc>
|
||||
<lastmod>2026-03-25</lastmod>
|
||||
<lastmod>2026-04-20</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bayareait.services/blog</loc>
|
||||
<lastmod>2026-03-25</lastmod>
|
||||
<lastmod>2026-04-20</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bayareait.services/contact</loc>
|
||||
<lastmod>2026-03-25</lastmod>
|
||||
<lastmod>2026-04-20</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bayareait.services/about</loc>
|
||||
<lastmod>2026-03-25</lastmod>
|
||||
<lastmod>2026-04-20</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bayareait.services/privacy-policy</loc>
|
||||
<lastmod>2026-04-20</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bayareait.services/terms-of-service</loc>
|
||||
<lastmod>2026-04-20</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bayareait.services/locations/it-support-corpus-christi</loc>
|
||||
<lastmod>2026-03-25</lastmod>
|
||||
<lastmod>2026-04-20</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bayareait.services/locations/it-support-portland-tx</loc>
|
||||
<lastmod>2026-03-25</lastmod>
|
||||
<lastmod>2026-04-20</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bayareait.services/locations/it-support-rockport-tx</loc>
|
||||
<lastmod>2026-03-25</lastmod>
|
||||
<lastmod>2026-04-20</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bayareait.services/locations/it-support-aransas-pass-tx</loc>
|
||||
<lastmod>2026-03-25</lastmod>
|
||||
<lastmod>2026-04-20</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bayareait.services/locations/it-support-kingsville-tx</loc>
|
||||
<lastmod>2026-03-25</lastmod>
|
||||
<lastmod>2026-04-20</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bayareait.services/services/web-design-corpus-christi</loc>
|
||||
<lastmod>2026-03-25</lastmod>
|
||||
<lastmod>2026-04-20</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bayareait.services/services/business-email-corpus-christi</loc>
|
||||
<lastmod>2026-03-25</lastmod>
|
||||
<lastmod>2026-04-20</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bayareait.services/services/it-help-desk</loc>
|
||||
<lastmod>2026-03-25</lastmod>
|
||||
<lastmod>2026-04-20</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bayareait.services/services/computer-support</loc>
|
||||
<lastmod>2026-03-25</lastmod>
|
||||
<lastmod>2026-04-20</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bayareait.services/services/business-email-services</loc>
|
||||
<lastmod>2026-03-25</lastmod>
|
||||
<lastmod>2026-04-20</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bayareait.services/services/domain-registration-dns-support</loc>
|
||||
<lastmod>2026-03-25</lastmod>
|
||||
<lastmod>2026-04-20</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bayareait.services/services/printer-scanner-installation</loc>
|
||||
<lastmod>2026-04-20</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bayareait.services/services/shared-drive</loc>
|
||||
<lastmod>2026-04-20</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bayareait.services/services/network-infrastructure-support</loc>
|
||||
<lastmod>2026-04-20</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bayareait.services/blog/it-support-small-business-corpus-christi</loc>
|
||||
<lastmod>2026-03-25</lastmod>
|
||||
<lastmod>2026-04-20</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bayareait.services/blog/outsourced-it-support-corpus-christi</loc>
|
||||
<lastmod>2026-03-25</lastmod>
|
||||
<lastmod>2026-04-20</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bayareait.services/blog/it-service-vs-inhouse-it</loc>
|
||||
<lastmod>2026-03-25</lastmod>
|
||||
<lastmod>2026-04-20</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bayareait.services/blog/common-it-problems-businesses-corpus-christi</loc>
|
||||
<lastmod>2026-03-25</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bayareait.services/blog/it-support-cost-corpus-christi</loc>
|
||||
<lastmod>2026-03-25</lastmod>
|
||||
<lastmod>2026-04-20</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bayareait.services/blog/business-email-vs-google-workspace-vs-microsoft-365</loc>
|
||||
<lastmod>2026-03-25</lastmod>
|
||||
<lastmod>2026-04-20</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
|
||||
@@ -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`,
|
||||
},
|
||||
{
|
||||
|
||||
188
server.mjs
Normal file
188
server.mjs
Normal file
@@ -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)' : ''}.`);
|
||||
});
|
||||
@@ -49,9 +49,9 @@ export const locationData: LocationData[] = [
|
||||
<p>Many local businesses choose outsourced IT support to gain access to professional expertise without the cost of an internal IT department.</p>
|
||||
|
||||
<h3>Why Local Businesses Choose Bay Area IT</h3>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
<ul>
|
||||
<li><strong>25+ years in the Coastal Bend</strong> — Deep roots in the local business community</li>
|
||||
<li><strong>30+ years in the Coastal Bend</strong> — Deep roots in the local business community</li>
|
||||
<li><strong>30+ local businesses supported</strong> — Hands-on experience across a range of industries</li>
|
||||
<li><strong>Fast response time</strong> — Quick remote support when something breaks, not a slow ticket queue</li>
|
||||
<li><strong>No jargon, no hidden fees</strong> — We explain what we do and charge what we quote</li>
|
||||
@@ -305,9 +305,9 @@ export const locationData: LocationData[] = [
|
||||
<p>As your local IT partner, we also support your website long-term alongside your other technology needs. Learn more about our broader <a href="/locations/it-support-corpus-christi">IT support services in Corpus Christi</a>.</p>
|
||||
|
||||
<h3>A Local Partner for Your Online Presence</h3>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
<ul>
|
||||
<li><strong>25+ years local</strong> — Deep experience with Coastal Bend businesses</li>
|
||||
<li><strong>30+ years local</strong> — Deep experience with Coastal Bend businesses</li>
|
||||
<li><strong>Full-service</strong> — Web design, domain, email, and IT support under one roof</li>
|
||||
<li><strong>30+ local clients</strong> — We understand what local businesses need to grow online</li>
|
||||
<li><strong>Transparent pricing</strong> — No hidden costs, no surprise invoices</li>
|
||||
@@ -368,10 +368,10 @@ export const locationData: LocationData[] = [
|
||||
<h3>Domain + Email, Handled Together</h3>
|
||||
<p>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 <a href="/services/business-email-services">business email services</a> or our <a href="/services/domain-registration-dns-support">domain and DNS support</a>.</p>
|
||||
|
||||
<h3>A Local IT Team With 25 Years of Experience</h3>
|
||||
<p>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.</p>
|
||||
<h3>A Local IT Team With 30 Years of Experience</h3>
|
||||
<p>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.</p>
|
||||
<ul>
|
||||
<li><strong>25+ years in Corpus Christi</strong> — Long-term local presence, not a new provider</li>
|
||||
<li><strong>30+ years in Corpus Christi</strong> — Long-term local presence, not a new provider</li>
|
||||
<li><strong>Fast response time</strong> — Quick remote support for email access issues</li>
|
||||
<li><strong>30+ local businesses supported</strong> — Proven experience with companies like yours</li>
|
||||
<li><strong>Clear, honest communication</strong> — No hidden costs, no confusing technical language</li>
|
||||
@@ -427,11 +427,11 @@ export const serviceData: ServiceData[] = [
|
||||
<p>Our help desk team in Corpus Christi ensures your employees stay productive by resolving technical problems quickly and efficiently.</p>
|
||||
<p>For broader support, consider our <a href="/services/computer-support">Computer Support</a> or <a href="/locations/it-support-corpus-christi">IT Support in Corpus Christi</a>.</p>
|
||||
|
||||
<h3>A Help Desk Backed by 25 Years of Local Experience</h3>
|
||||
<p>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.</p>
|
||||
<h3>A Help Desk Backed by 30 Years of Local Experience</h3>
|
||||
<p>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.</p>
|
||||
<ul>
|
||||
<li><strong>Fast response time</strong> — Employees get help when they need it, not hours later</li>
|
||||
<li><strong>25+ years of IT experience</strong> — No problem is new to us</li>
|
||||
<li><strong>30+ years of IT experience</strong> — No problem is new to us</li>
|
||||
<li><strong>30+ local businesses supported</strong> — Across the Coastal Bend</li>
|
||||
<li><strong>No jargon</strong> — We communicate clearly with employees at every level</li>
|
||||
</ul>
|
||||
@@ -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: `
|
||||
<p>Using a free Gmail or Yahoo address for business communication looks unprofessional and can hurt client trust. A domain-based email address — like <strong>yourname@yourbusiness.com</strong> — signals legitimacy and helps you stand out.</p>
|
||||
<p>We set up and support professional business email for small businesses in the Corpus Christi area, from initial configuration to ongoing maintenance and troubleshooting.</p>
|
||||
@@ -544,8 +544,8 @@ export const serviceData: ServiceData[] = [
|
||||
<h3>What We Set Up</h3>
|
||||
<p>Our business email services include:</p>
|
||||
<ul>
|
||||
<li>Microsoft 365 (Outlook) business email setup and licensing</li>
|
||||
<li>Google Workspace (Gmail for Business) setup and configuration</li>
|
||||
<li>Microsoft Outlook business email setup and licensing</li>
|
||||
<li>iPhone and iPad setup and configuration</li>
|
||||
<li>Domain-based email addresses (info@, contact@, yourname@)</li>
|
||||
<li>Email DNS configuration — MX, SPF, DKIM, and DMARC records</li>
|
||||
<li>Spam filter configuration and email security hardening</li>
|
||||
@@ -561,9 +561,9 @@ export const serviceData: ServiceData[] = [
|
||||
<p>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.</p>
|
||||
|
||||
<h3>Why Businesses Trust Us With Their Email Setup</h3>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
<ul>
|
||||
<li><strong>25+ years of IT experience</strong> — We have seen every email configuration problem that exists</li>
|
||||
<li><strong>30+ years of IT experience</strong> — We have seen every email configuration problem that exists</li>
|
||||
<li><strong>Clear, jargon-free communication</strong> — We explain SPF, DKIM, and DMARC in plain language</li>
|
||||
<li><strong>Fast response</strong> — Based in Corpus Christi, not an overseas support queue</li>
|
||||
<li><strong>30+ local businesses supported</strong> — Proven track record with Coastal Bend companies</li>
|
||||
@@ -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[] = [
|
||||
<p>Ready to set up your business email on a proper domain? See our <a href="/services/business-email-services">business email services</a> for the full picture.</p>
|
||||
|
||||
<h3>Technical Expertise, Plain-Language Communication</h3>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
<ul>
|
||||
<li><strong>25+ years of IT and DNS experience</strong> — Every configuration scenario covered</li>
|
||||
<li><strong>30+ years of IT and DNS experience</strong> — Every configuration scenario covered</li>
|
||||
<li><strong>No jargon</strong> — MX records and DMARC explained in terms that make sense</li>
|
||||
<li><strong>Fast response</strong> — Local team in Corpus Christi, reachable when it is urgent</li>
|
||||
</ul>
|
||||
@@ -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: `
|
||||
<p>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.</p>
|
||||
<p>From a single front-desk printer to a multi-user office setup, we make sure devices are configured correctly for your network and users.</p>
|
||||
|
||||
<h3>What We Set Up</h3>
|
||||
<ul>
|
||||
<li>Network printer installation and driver deployment</li>
|
||||
<li>Scanner setup including scan-to-email and scan-to-folder workflows</li>
|
||||
<li>User access and workstation configuration</li>
|
||||
<li>Troubleshooting for connectivity and printing errors</li>
|
||||
<li>Best-practice placement and basic print reliability checks</li>
|
||||
</ul>
|
||||
|
||||
<h3>Why This Service Matters</h3>
|
||||
<p>Printer issues waste time because they interrupt simple daily tasks. A proper setup reduces recurring support tickets, user confusion, and document handling delays.</p>
|
||||
<p>If your team also needs help with computers or file access, see our <a href="/services/computer-support">computer support services</a> and <a href="/services/shared-drive">shared drive setup</a>.</p>
|
||||
|
||||
<h3>Configured for Real Office Workflows</h3>
|
||||
<p>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.</p>
|
||||
<ul>
|
||||
<li><strong>Reliable setup</strong> — Correct drivers, permissions, and network visibility</li>
|
||||
<li><strong>Clear user experience</strong> — Staff know where to print and scan without guesswork</li>
|
||||
<li><strong>Fast support</strong> — Local team available when printer issues return</li>
|
||||
</ul>
|
||||
`,
|
||||
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: `
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
|
||||
<h3>What We Configure</h3>
|
||||
<ul>
|
||||
<li>Shared drive or shared folder setup</li>
|
||||
<li>Folder structure planning by department or role</li>
|
||||
<li>User and group permissions</li>
|
||||
<li>Mapped drive access on employee workstations</li>
|
||||
<li>Basic backup and recovery alignment</li>
|
||||
</ul>
|
||||
|
||||
<h3>Why Shared File Access Breaks So Often</h3>
|
||||
<p>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.</p>
|
||||
<p>If you also need help with the network or office computers connected to those files, see our <a href="/services/network-infrastructure-support">network infrastructure support</a> and <a href="/services/computer-support">computer support</a>.</p>
|
||||
|
||||
<h3>Built for Team Access, Not Guesswork</h3>
|
||||
<p>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.</p>
|
||||
<ul>
|
||||
<li><strong>Organized access</strong> — Folder structure that reflects your business workflow</li>
|
||||
<li><strong>Permission control</strong> — Users only see what they should see</li>
|
||||
<li><strong>Long-term maintainability</strong> — Easy to update as your team changes</li>
|
||||
</ul>
|
||||
`,
|
||||
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: `
|
||||
<p>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.</p>
|
||||
<p>We help businesses set up and maintain practical network infrastructure that supports stable connectivity, clear troubleshooting, and secure access.</p>
|
||||
|
||||
<h3>What We Support</h3>
|
||||
<ul>
|
||||
<li>Router, firewall, and switch configuration</li>
|
||||
<li>Wi-Fi and access point setup</li>
|
||||
<li>Basic network segmentation and device organization</li>
|
||||
<li>Troubleshooting for slow or unstable connections</li>
|
||||
<li>Network cleanup for growing offices and mixed device environments</li>
|
||||
</ul>
|
||||
|
||||
<h3>When Businesses Need Network Help</h3>
|
||||
<p>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.</p>
|
||||
<p>Need help with the systems running on top of that network? See our <a href="/services/shared-drive">shared drive support</a> and <a href="/services/it-help-desk">IT help desk services</a>.</p>
|
||||
|
||||
<h3>Designed for Stability and Supportability</h3>
|
||||
<p>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.</p>
|
||||
<ul>
|
||||
<li><strong>Better reliability</strong> — Fewer mystery disconnects and weak spots</li>
|
||||
<li><strong>Clearer structure</strong> — Equipment and traffic organized sensibly</li>
|
||||
<li><strong>Local support</strong> — Fast troubleshooting when the office depends on connectivity</li>
|
||||
</ul>
|
||||
`,
|
||||
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: `
|
||||
<p>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.</p>
|
||||
@@ -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: `
|
||||
|
||||
<p>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?</p>
|
||||
<p>Here is a straightforward comparison of your three main options.</p>
|
||||
|
||||
<h2>Option 1: Free Gmail or Yahoo (Not Recommended for Business)</h2>
|
||||
<p>Free consumer email accounts are tempting because they cost nothing, but they come with real business drawbacks:</p>
|
||||
<ul>
|
||||
<li><strong>No custom domain</strong> — Your address is name@gmail.com, not name@yourbusiness.com</li>
|
||||
<li><strong>Lower trust</strong> — Clients and vendors notice, and it signals a lack of professionalism</li>
|
||||
<li><strong>No admin control</strong> — You cannot manage employee accounts, reset passwords, or audit access</li>
|
||||
<li><strong>Deliverability risks</strong> — Free accounts have weaker authentication, increasing spam likelihood</li>
|
||||
<li><strong>No business continuity</strong> — If an employee leaves, you may lose access to that inbox</li>
|
||||
</ul>
|
||||
<p>For a solo freelancer just starting out, a free account is acceptable. For any established business, it sends the wrong signal.</p>
|
||||
|
||||
<h2>Option 2: Google Workspace (Gmail for Business)</h2>
|
||||
<p>Google Workspace gives you Gmail with your own domain name, plus the full Google productivity suite.</p>
|
||||
<ul>
|
||||
<li><strong>Familiar interface</strong> — If you already use Gmail, the learning curve is nearly zero</li>
|
||||
<li><strong>Included tools</strong> — Google Drive, Docs, Sheets, Meet, Calendar, and Chat</li>
|
||||
<li><strong>Strong mobile apps</strong> — Works seamlessly on Android and iPhone</li>
|
||||
<li><strong>Admin console</strong> — Add and remove users, manage devices, set security policies</li>
|
||||
<li><strong>Starting price</strong> — Business Starter plan begins around $6/user/month</li>
|
||||
</ul>
|
||||
<p><strong>Best for:</strong> Teams that prefer a browser-based workflow, already use Google tools, or work heavily on mobile.</p>
|
||||
|
||||
<h2>Option 3: Microsoft 365 Business (Outlook for Business)</h2>
|
||||
<p>Microsoft 365 is the standard in most professional and corporate environments, bundling Outlook with the full Office suite.</p>
|
||||
<ul>
|
||||
<li><strong>Outlook email</strong> — Powerful calendar, contacts, and task integration</li>
|
||||
<li><strong>Included apps</strong> — Word, Excel, PowerPoint, Teams, SharePoint, and OneDrive</li>
|
||||
<li><strong>Desktop apps included</strong> — Most plans include installed Office apps, not just browser versions</li>
|
||||
<li><strong>Strong compliance tools</strong> — Better fit for regulated industries (healthcare, finance, legal)</li>
|
||||
<li><strong>Starting price</strong> — Business Basic begins around $6/user/month; Business Standard around $12.50</li>
|
||||
</ul>
|
||||
<p><strong>Best for:</strong> Teams that rely on Excel, Word, or PowerPoint; businesses in regulated industries; offices that prefer desktop applications over browser tools.</p>
|
||||
|
||||
<h2>Which One Should You Choose?</h2>
|
||||
<p>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:</p>
|
||||
<ul>
|
||||
<li>What tools your team already uses day-to-day</li>
|
||||
<li>Whether you need desktop Office apps or browser-based work is fine</li>
|
||||
<li>Whether any clients or vendors require compatibility with specific formats</li>
|
||||
</ul>
|
||||
<p>What matters most is getting off free consumer email and onto a platform with a custom domain, proper authentication records, and admin control.</p>
|
||||
|
||||
<h2>The Setup Step Most Businesses Skip</h2>
|
||||
<p>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.</p>
|
||||
<p>Ready to get set up? See our <a href="/services/business-email-services">business email services</a> for how we handle the full setup — platform selection, DNS configuration, migration, and ongoing support.</p>
|
||||
`
|
||||
}
|
||||
];
|
||||
image: "/images/blog/business-email-comparison-new.webp",
|
||||
content: `
|
||||
|
||||
<p>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.</p>
|
||||
|
||||
<h2>Start Here: Free Email Is Fine for Personal Use, Not for Business</h2>
|
||||
<p>Free Gmail, Yahoo, and similar inboxes are easy to start with, but they create long-term problems for established businesses:</p>
|
||||
<ul>
|
||||
<li><strong>No branded domain</strong> - You are advertising gmail.com instead of your own company name</li>
|
||||
<li><strong>Weak account control</strong> - There is no proper admin workflow for employee turnover, security, or shared ownership</li>
|
||||
<li><strong>Deliverability issues</strong> - Business domains need SPF, DKIM, and DMARC configured correctly to stay out of spam folders</li>
|
||||
<li><strong>Poor continuity</strong> - Important client communication can stay tied to one person's personal inbox</li>
|
||||
<li><strong>Lower trust</strong> - Clients and vendors expect a professional address for a real business</li>
|
||||
</ul>
|
||||
<p>If you have customers, employees, or recurring vendor communication, free email is usually the wrong long-term setup.</p>
|
||||
|
||||
<h2>Google Workspace: Best for Teams That Already Live in Gmail</h2>
|
||||
<p>Google Workspace is usually the easiest upgrade for businesses that already like Gmail and prefer browser-based tools.</p>
|
||||
<ul>
|
||||
<li><strong>Very low learning curve</strong> - Staff who already know Gmail adapt quickly</li>
|
||||
<li><strong>Strong collaboration tools</strong> - Drive, Docs, Sheets, Meet, and shared calendars are simple and fast</li>
|
||||
<li><strong>Good mobile experience</strong> - Works well for teams using iPhones, Android phones, and web apps</li>
|
||||
<li><strong>Simple admin controls</strong> - You can add users, suspend accounts, enforce MFA, and manage devices</li>
|
||||
<li><strong>Clean fit for smaller teams</strong> - Especially good when the company wants lightweight, cloud-first workflows</li>
|
||||
</ul>
|
||||
<p><strong>Choose Google Workspace if:</strong> your team likes Gmail, collaborates heavily in browser tools, and does not depend on desktop Office files all day.</p>
|
||||
|
||||
<h2>Microsoft 365: Best for Teams Built Around Outlook and Office</h2>
|
||||
<p>Microsoft 365 is usually the better choice when the business already depends on Outlook, Excel, Word, Teams, and traditional office workflows.</p>
|
||||
<ul>
|
||||
<li><strong>Strong Outlook environment</strong> - Familiar for many office teams and admin-heavy workflows</li>
|
||||
<li><strong>Desktop Office apps</strong> - Important when staff live in Excel, Word, and PowerPoint</li>
|
||||
<li><strong>Broader business controls</strong> - Useful for permissions, device management, and more structured environments</li>
|
||||
<li><strong>Good fit for regulated businesses</strong> - Often preferred in healthcare, finance, legal, and other documentation-heavy teams</li>
|
||||
<li><strong>Integrated Microsoft ecosystem</strong> - Teams, OneDrive, SharePoint, and Office all work together</li>
|
||||
</ul>
|
||||
<p><strong>Choose Microsoft 365 if:</strong> your team uses Outlook and Office heavily, exchanges Office documents constantly, or wants a more traditional office software stack.</p>
|
||||
|
||||
<h2>Quick Decision Guide</h2>
|
||||
<p>For most small businesses in Corpus Christi, the platform decision comes down to a few practical questions:</p>
|
||||
<ul>
|
||||
<li><strong>Already happy with Gmail?</strong> Google Workspace is usually the cleanest move</li>
|
||||
<li><strong>Already dependent on Outlook, Excel, or Word?</strong> Microsoft 365 is usually the safer fit</li>
|
||||
<li><strong>Need the fewest moving parts for a small team?</strong> Google Workspace often feels simpler</li>
|
||||
<li><strong>Need more formal desktop-app workflows and tighter Microsoft integration?</strong> Microsoft 365 usually wins</li>
|
||||
</ul>
|
||||
<p>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.</p>
|
||||
|
||||
<h2>The Setup Work Matters More Than Most Businesses Expect</h2>
|
||||
<p>Platform choice is only part of the job. The actual business risk usually appears during setup and migration:</p>
|
||||
<ul>
|
||||
<li>domain verification and DNS changes</li>
|
||||
<li>SPF, DKIM, and DMARC configuration</li>
|
||||
<li>mailbox migration from old providers</li>
|
||||
<li>phone, tablet, and desktop app setup</li>
|
||||
<li>shared mailbox, alias, and forwarding decisions</li>
|
||||
<li>user onboarding and lockout prevention</li>
|
||||
</ul>
|
||||
<p>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.</p>
|
||||
|
||||
<h2>What We Recommend for Small Businesses</h2>
|
||||
<p>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.</p>
|
||||
|
||||
<p>If you want help choosing the right platform and getting everything configured correctly, see our <a href="/services/business-email-services">business email services</a>. We handle platform selection, DNS setup, migration, device setup, and ongoing support for businesses in Corpus Christi and the Coastal Bend.</p>
|
||||
`
|
||||
}
|
||||
];
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
|
||||
81
src/hooks/useContactForm.ts
Normal file
81
src/hooks/useContactForm.ts
Normal file
@@ -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<ContactFormStatus>('idle');
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
|
||||
const resetFeedback = () => {
|
||||
if (status === 'idle') {
|
||||
return;
|
||||
}
|
||||
|
||||
setStatus('idle');
|
||||
setErrorMessage('');
|
||||
};
|
||||
|
||||
const submitContactForm = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
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,
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = () => {
|
||||
<>
|
||||
<SEO
|
||||
title="About Bay Area IT | Local IT Support in Corpus Christi"
|
||||
description="Learn about Bay Area IT, a local IT partner serving Corpus Christi and the Coastal Bend with practical support, reliable service, and over 25 years of experience."
|
||||
description="Learn about Bay Area IT, a local IT partner serving Corpus Christi and the Coastal Bend with practical support, reliable service, and over 30 years of experience."
|
||||
keywords={['about Bay Area IT', 'Corpus Christi IT company', 'local IT support Coastal Bend']}
|
||||
canonicalUrl="https://bayareait.services/about"
|
||||
/>
|
||||
@@ -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.
|
||||
</motion.p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -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) => (
|
||||
<div key={index} className="p-4">
|
||||
|
||||
@@ -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 = () => {
|
||||
<>
|
||||
<SEO
|
||||
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."
|
||||
description="Read practical IT guidance for Corpus Christi and Coastal Bend businesses, from outsourced IT support to business email and local service coverage."
|
||||
keywords={['Corpus Christi IT blog', 'managed IT insights', 'business IT support articles']}
|
||||
canonicalUrl="https://bayareait.services/blog"
|
||||
/>
|
||||
@@ -45,7 +45,7 @@ const BlogPage: React.FC = () => {
|
||||
|
||||
<section className="py-16 px-6 bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.05),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.05),rgba(255,255,255,0))]">
|
||||
<div className="max-w-5xl mx-auto space-y-16">
|
||||
{blogPostData.filter(post => !post.redirect).map((post, index) => (
|
||||
{orderedBlogPostData.filter(post => !post.redirect).map((post, index) => (
|
||||
<Link
|
||||
key={post.id}
|
||||
to={`/${post.slug}`}
|
||||
@@ -60,11 +60,11 @@ const BlogPage: React.FC = () => {
|
||||
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"
|
||||
>
|
||||
<div className="h-64 md:h-auto overflow-hidden relative">
|
||||
<img
|
||||
src={post.image || '/images/blog/default.png'}
|
||||
alt={post.h1}
|
||||
loading={index === 0 ? 'eager' : 'lazy'}
|
||||
decoding="async"
|
||||
<img
|
||||
src={post.image || '/images/blog/business-email-comparison-new.webp'}
|
||||
alt={post.h1}
|
||||
loading={index === 0 ? 'eager' : 'lazy'}
|
||||
decoding="async"
|
||||
fetchPriority={index === 0 ? 'high' : 'low'}
|
||||
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105"
|
||||
/>
|
||||
|
||||
@@ -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<HTMLFormElement>) => {
|
||||
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 (
|
||||
<>
|
||||
<SEO
|
||||
title="Contact Bay Area IT | Free IT Assessment in Corpus Christi"
|
||||
@@ -52,200 +26,206 @@ const ContactPage: React.FC = () => {
|
||||
canonicalUrl="https://bayareait.services/contact"
|
||||
/>
|
||||
<div className="pt-20 min-h-screen bg-background-light dark:bg-background-dark relative overflow-x-hidden">
|
||||
<div className="absolute top-0 left-0 right-0 h-[800px] bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.2),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.25),rgba(255,255,255,0))] pointer-events-none" />
|
||||
<div className="absolute bottom-0 right-0 w-[500px] h-[500px] bg-gray-100/50 dark:bg-white/5 rounded-full blur-[100px] pointer-events-none" />
|
||||
{/* Hero */}
|
||||
<section className="py-20 px-6 text-center border-b border-white/5 relative z-10">
|
||||
<h1 className="font-display text-4xl md:text-5xl font-bold mb-6 text-white">
|
||||
Let's talk about <br /><span className="text-gray-500">your IT needs</span>
|
||||
</h1>
|
||||
<p className="text-xl text-gray-400 max-w-2xl mx-auto">
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="py-24 px-6 bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.05),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.05),rgba(255,255,255,0))]">
|
||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-16">
|
||||
|
||||
{/* Left: Contact Form */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
className="bg-white/5 backdrop-blur-sm p-8 md:p-10 rounded-3xl shadow-xl border border-white/10"
|
||||
>
|
||||
<h3 className="text-2xl font-bold mb-8 text-white">Send us a message</h3>
|
||||
<form className="space-y-6" onSubmit={handleSubmit}>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label htmlFor="name" className="block text-sm font-medium text-gray-300 mb-2">Name *</label>
|
||||
<div className="absolute top-0 left-0 right-0 h-[800px] bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.2),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.25),rgba(255,255,255,0))] pointer-events-none" />
|
||||
<div className="absolute bottom-0 right-0 w-[500px] h-[500px] bg-gray-100/50 dark:bg-white/5 rounded-full blur-[100px] pointer-events-none" />
|
||||
{/* Hero */}
|
||||
<section className="py-20 px-6 text-center border-b border-white/5 relative z-10">
|
||||
<h1 className="font-display text-4xl md:text-5xl font-bold mb-6 text-white">
|
||||
Let's talk about <br /><span className="text-gray-500">your IT needs</span>
|
||||
</h1>
|
||||
<p className="text-xl text-gray-400 max-w-2xl mx-auto">
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="py-24 px-6 bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(0,0,0,0.05),rgba(0,0,0,0))] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.05),rgba(255,255,255,0))]">
|
||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-16">
|
||||
|
||||
{/* Left: Contact Form */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
className="bg-white/5 backdrop-blur-sm p-8 md:p-10 rounded-3xl shadow-xl border border-white/10"
|
||||
>
|
||||
<h3 className="text-2xl font-bold mb-8 text-white">Send us a message</h3>
|
||||
<form className="space-y-6" onChange={resetFeedback} onSubmit={submitContactForm}>
|
||||
<input type="text" name="website" tabIndex={-1} autoComplete="off" className="hidden" aria-hidden="true" />
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label htmlFor="name" className="block text-sm font-medium text-gray-300 mb-2">Name *</label>
|
||||
<input type="text" id="name" name="name" required className="w-full px-4 py-3 rounded-xl bg-black/20 border border-white/10 focus:border-white/30 focus:ring-1 focus:ring-white/30 outline-none transition-all text-white placeholder-gray-600" placeholder="John Doe" />
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="phone" className="block text-sm font-medium text-gray-300 mb-2">Phone</label>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="phone" className="block text-sm font-medium text-gray-300 mb-2">Phone</label>
|
||||
<input type="tel" id="phone" name="phone" className="w-full px-4 py-3 rounded-xl bg-black/20 border border-white/10 focus:border-white/30 focus:ring-1 focus:ring-white/30 outline-none transition-all text-white placeholder-gray-600" placeholder="(555) 000-0000" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-300 mb-2">Email *</label>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-300 mb-2">Email *</label>
|
||||
<input type="email" id="email" name="email" required className="w-full px-4 py-3 rounded-xl bg-black/20 border border-white/10 focus:border-white/30 focus:ring-1 focus:ring-white/30 outline-none transition-all text-white placeholder-gray-600" placeholder="john@company.com" />
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="company" className="block text-sm font-medium text-gray-300 mb-2">Company</label>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="company" className="block text-sm font-medium text-gray-300 mb-2">Company</label>
|
||||
<input type="text" id="company" name="company" className="w-full px-4 py-3 rounded-xl bg-black/20 border border-white/10 focus:border-white/30 focus:ring-1 focus:ring-white/30 outline-none transition-all text-white placeholder-gray-600" placeholder="Acme Inc." />
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="message" className="block text-sm font-medium text-gray-300 mb-2">Message *</label>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="message" className="block text-sm font-medium text-gray-300 mb-2">Message *</label>
|
||||
<textarea id="message" name="message" rows={4} required className="w-full px-4 py-3 rounded-xl bg-black/20 border border-white/10 focus:border-white/30 focus:ring-1 focus:ring-white/30 outline-none transition-all text-white placeholder-gray-600 resize-none" placeholder="How can we help you?"></textarea>
|
||||
</div>
|
||||
<button type="submit" className="w-full py-4 bg-white text-black font-bold rounded-xl transition-all hover:bg-gray-200 hover:scale-[1.02] shadow-[0_0_20px_rgba(255,255,255,0.1)]">
|
||||
Send Message
|
||||
<button type="submit" disabled={isSubmitting} className="selection-inverse w-full py-4 bg-white text-black font-bold rounded-xl transition-all hover:bg-gray-200 hover:scale-[1.02] shadow-[0_0_20px_rgba(255,255,255,0.1)] disabled:cursor-not-allowed disabled:opacity-70 disabled:hover:scale-100">
|
||||
{isSubmitting ? 'Sending...' : 'Send Message'}
|
||||
</button>
|
||||
{isSubmitted && (
|
||||
<p className="text-sm text-gray-400">
|
||||
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.
|
||||
</p>
|
||||
)}
|
||||
{hasError && (
|
||||
<p className="text-sm text-red-300">
|
||||
{errorMessage}
|
||||
</p>
|
||||
)}
|
||||
</form>
|
||||
</motion.div>
|
||||
|
||||
{/* Right: FAQ & Info */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="space-y-12"
|
||||
>
|
||||
{/* Contact Info */}
|
||||
<div className="grid sm:grid-cols-2 gap-8">
|
||||
<div className="bg-white/5 backdrop-blur-sm p-6 rounded-2xl border border-white/10 hover:border-white/20 transition-colors">
|
||||
<div className="w-10 h-10 bg-white/10 rounded-full flex items-center justify-center text-white mb-4">
|
||||
<span className="material-symbols-outlined">call</span>
|
||||
</div>
|
||||
<h4 className="font-bold text-white mb-1">Phone</h4>
|
||||
<p className="text-gray-400">(361) 765-8400</p>
|
||||
</div>
|
||||
<div className="bg-white/5 backdrop-blur-sm p-6 rounded-2xl border border-white/10 hover:border-white/20 transition-colors">
|
||||
<div className="w-10 h-10 bg-white/10 rounded-full flex items-center justify-center text-white mb-4">
|
||||
<span className="material-symbols-outlined">location_on</span>
|
||||
</div>
|
||||
<h4 className="font-bold text-white mb-1">Address</h4>
|
||||
<p className="text-gray-400">1001 Blucher St,<br />Corpus Christi, TX 78401</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white/5 backdrop-blur-sm p-8 rounded-2xl border border-white/10">
|
||||
<h4 className="font-bold text-white mb-4">Hours & Area</h4>
|
||||
<p className="text-gray-400 mb-2"><span className="font-semibold text-white">Mon - Fri:</span> 8:00 AM - 6:00 PM</p>
|
||||
<p className="text-gray-500 text-sm mb-4">(Emergency support: 24/7)</p>
|
||||
<p className="text-gray-400"><span className="font-semibold text-white">Service Area:</span> Corpus Christi & the Coastal Bend (including Portland, Rockport, Aransas Pass, Kingsville, Port Aransas)</p>
|
||||
</div>
|
||||
|
||||
{/* FAQ */}
|
||||
<div>
|
||||
<h3 className="text-2xl font-bold mb-6 text-white">Quick Answers</h3>
|
||||
<div className="space-y-4">
|
||||
{faqs.map((faq, index) => (
|
||||
<div key={index} className="bg-white/5 backdrop-blur-sm p-6 rounded-2xl border border-white/10 hover:border-white/30 transition-colors">
|
||||
<h4 className="font-bold text-white mb-2">{faq.q}</h4>
|
||||
<p className="text-gray-400 text-sm leading-relaxed">{faq.a}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* What Happens Next Section */}
|
||||
<section className="py-24 px-6 border-t border-white/5">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<h2 className="font-display text-3xl md:text-4xl font-bold text-center mb-16 text-white">
|
||||
What happens next?
|
||||
</h2>
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
{[
|
||||
{
|
||||
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) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: index * 0.1 }}
|
||||
className="bg-white/5 backdrop-blur-sm p-8 rounded-2xl border border-white/10 hover:bg-white/10 transition-colors relative overflow-hidden group"
|
||||
>
|
||||
<div className="absolute top-0 right-0 p-8 opacity-10 font-display text-8xl font-bold text-white group-hover:scale-110 transition-transform select-none pointer-events-none">
|
||||
{item.step}
|
||||
</div>
|
||||
<div className="relative z-10">
|
||||
<div className="w-12 h-12 bg-white/10 rounded-full flex items-center justify-center text-white mb-6 border border-white/10">
|
||||
<span className="material-symbols-outlined">
|
||||
{index === 0 ? 'schedule_send' : index === 1 ? 'phone_in_talk' : 'contract_edit'}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-white mb-3">{item.title}</h3>
|
||||
<p className="text-gray-400 leading-relaxed">{item.description}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Service Area Section */}
|
||||
<section className="py-24 px-6 relative overflow-hidden">
|
||||
<div className="absolute inset-0 bg-white/5"></div>
|
||||
<div className="max-w-7xl mx-auto relative z-10">
|
||||
<div className="grid lg:grid-cols-2 gap-16 items-center">
|
||||
<div>
|
||||
<h2 className="font-display text-3xl md:text-4xl font-bold mb-6 text-white">
|
||||
Our Service Area
|
||||
</h2>
|
||||
<p className="text-lg text-gray-400 mb-8 leading-relaxed">
|
||||
Proudly serving Corpus Christi, Portland, Rockport, Aransas Pass, Kingsville, Port Aransas, and the entire Coastal Bend region.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{['Corpus Christi', 'Portland', 'Rockport', 'Aransas Pass', 'Kingsville', 'Port Aransas'].map((city) => (
|
||||
<div key={city} className="flex items-center gap-2 px-4 py-2 rounded-full bg-white/5 border border-white/10 text-gray-300">
|
||||
<span className="material-symbols-outlined text-sm text-white">location_on</span>
|
||||
{city}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-[400px] bg-[#1a1a1a] rounded-3xl overflow-hidden border border-white/10 shadow-2xl relative">
|
||||
<iframe
|
||||
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3532.1843778537446!2d-97.39864222453538!3d27.79426697613324!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x86685f8090787b65%3A0x6762397985799732!2s1001%20Blucher%20St%2C%20Corpus%20Christi%2C%20TX%2078401!5e0!3m2!1sen!2sus!4v1709420000000!5m2!1sen!2sus"
|
||||
width="100%"
|
||||
height="100%"
|
||||
style={{ border: 0 }}
|
||||
allowFullScreen
|
||||
loading="lazy"
|
||||
referrerPolicy="no-referrer-when-downgrade"
|
||||
></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</motion.div>
|
||||
|
||||
{/* Right: FAQ & Info */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="space-y-12"
|
||||
>
|
||||
{/* Contact Info */}
|
||||
<div className="grid sm:grid-cols-2 gap-8">
|
||||
<div className="bg-white/5 backdrop-blur-sm p-6 rounded-2xl border border-white/10 hover:border-white/20 transition-colors">
|
||||
<div className="w-10 h-10 bg-white/10 rounded-full flex items-center justify-center text-white mb-4">
|
||||
<span className="material-symbols-outlined">call</span>
|
||||
</div>
|
||||
<h4 className="font-bold text-white mb-1">Phone</h4>
|
||||
<p className="text-gray-400">(361) 765-8400</p>
|
||||
</div>
|
||||
<div className="bg-white/5 backdrop-blur-sm p-6 rounded-2xl border border-white/10 hover:border-white/20 transition-colors">
|
||||
<div className="w-10 h-10 bg-white/10 rounded-full flex items-center justify-center text-white mb-4">
|
||||
<span className="material-symbols-outlined">location_on</span>
|
||||
</div>
|
||||
<h4 className="font-bold text-white mb-1">Address</h4>
|
||||
<p className="text-gray-400">1001 Blucher St,<br />Corpus Christi, TX 78401</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white/5 backdrop-blur-sm p-8 rounded-2xl border border-white/10">
|
||||
<h4 className="font-bold text-white mb-4">Hours & Area</h4>
|
||||
<p className="text-gray-400 mb-2"><span className="font-semibold text-white">Mon - Fri:</span> 8:00 AM - 6:00 PM</p>
|
||||
<p className="text-gray-500 text-sm mb-4">(Emergency support: 24/7)</p>
|
||||
<p className="text-gray-400"><span className="font-semibold text-white">Service Area:</span> Corpus Christi & the Coastal Bend (including Portland, Rockport, Aransas Pass, Kingsville, Port Aransas)</p>
|
||||
</div>
|
||||
|
||||
{/* FAQ */}
|
||||
<div>
|
||||
<h3 className="text-2xl font-bold mb-6 text-white">Quick Answers</h3>
|
||||
<div className="space-y-4">
|
||||
{faqs.map((faq, index) => (
|
||||
<div key={index} className="bg-white/5 backdrop-blur-sm p-6 rounded-2xl border border-white/10 hover:border-white/30 transition-colors">
|
||||
<h4 className="font-bold text-white mb-2">{faq.q}</h4>
|
||||
<p className="text-gray-400 text-sm leading-relaxed">{faq.a}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* What Happens Next Section */}
|
||||
<section className="py-24 px-6 border-t border-white/5">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<h2 className="font-display text-3xl md:text-4xl font-bold text-center mb-16 text-white">
|
||||
What happens next?
|
||||
</h2>
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
{[
|
||||
{
|
||||
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) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: index * 0.1 }}
|
||||
className="bg-white/5 backdrop-blur-sm p-8 rounded-2xl border border-white/10 hover:bg-white/10 transition-colors relative overflow-hidden group"
|
||||
>
|
||||
<div className="absolute top-0 right-0 p-8 opacity-10 font-display text-8xl font-bold text-white group-hover:scale-110 transition-transform select-none pointer-events-none">
|
||||
{item.step}
|
||||
</div>
|
||||
<div className="relative z-10">
|
||||
<div className="w-12 h-12 bg-white/10 rounded-full flex items-center justify-center text-white mb-6 border border-white/10">
|
||||
<span className="material-symbols-outlined">
|
||||
{index === 0 ? 'schedule_send' : index === 1 ? 'phone_in_talk' : 'contract_edit'}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-white mb-3">{item.title}</h3>
|
||||
<p className="text-gray-400 leading-relaxed">{item.description}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Service Area Section */}
|
||||
<section className="py-24 px-6 relative overflow-hidden">
|
||||
<div className="absolute inset-0 bg-white/5"></div>
|
||||
<div className="max-w-7xl mx-auto relative z-10">
|
||||
<div className="grid lg:grid-cols-2 gap-16 items-center">
|
||||
<div>
|
||||
<h2 className="font-display text-3xl md:text-4xl font-bold mb-6 text-white">
|
||||
Our Service Area
|
||||
</h2>
|
||||
<p className="text-lg text-gray-400 mb-8 leading-relaxed">
|
||||
Proudly serving Corpus Christi, Portland, Rockport, Aransas Pass, Kingsville, Port Aransas, and the entire Coastal Bend region.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{['Corpus Christi', 'Portland', 'Rockport', 'Aransas Pass', 'Kingsville', 'Port Aransas'].map((city) => (
|
||||
<div key={city} className="flex items-center gap-2 px-4 py-2 rounded-full bg-white/5 border border-white/10 text-gray-300">
|
||||
<span className="material-symbols-outlined text-sm text-white">location_on</span>
|
||||
{city}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-[400px] bg-[#1a1a1a] rounded-3xl overflow-hidden border border-white/10 shadow-2xl relative">
|
||||
<iframe
|
||||
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3532.1843778537446!2d-97.39864222453538!3d27.79426697613324!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x86685f8090787b65%3A0x6762397985799732!2s1001%20Blucher%20St%2C%20Corpus%20Christi%2C%20TX%2078401!5e0!3m2!1sen!2sus!4v1709420000000!5m2!1sen!2sus"
|
||||
width="100%"
|
||||
height="100%"
|
||||
style={{ border: 0 }}
|
||||
allowFullScreen
|
||||
loading="lazy"
|
||||
referrerPolicy="no-referrer-when-downgrade"
|
||||
></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContactPage;
|
||||
|
||||
export default ContactPage;
|
||||
|
||||
@@ -112,11 +112,11 @@ const LocationPage: React.FC<LocationPageProps> = ({ data }) => {
|
||||
|
||||
<div className="min-h-screen bg-background-light dark:bg-background-dark relative overflow-x-hidden">
|
||||
{/* Hero Section */}
|
||||
<section
|
||||
ref={containerRef}
|
||||
onMouseMove={isInteractive ? handleMouseMove : undefined}
|
||||
className="relative min-h-[90vh] flex items-center justify-center overflow-hidden pt-20 group"
|
||||
>
|
||||
<section
|
||||
ref={containerRef}
|
||||
onMouseMove={isInteractive ? handleMouseMove : undefined}
|
||||
className="relative flex min-h-[32rem] items-center justify-center overflow-hidden pt-24 pb-14 md:min-h-[36rem] md:pt-28 md:pb-16 lg:min-h-[40rem] lg:pt-32 lg:pb-20 group"
|
||||
>
|
||||
{/* Parallax Background */}
|
||||
<div className="absolute inset-0 z-0 pointer-events-none">
|
||||
<div ref={parallaxWrapperRef} className="absolute w-full h-[120%] -top-[10%] left-0">
|
||||
@@ -168,14 +168,14 @@ const LocationPage: React.FC<LocationPageProps> = ({ data }) => {
|
||||
</p>
|
||||
|
||||
<div className="hero-stagger flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||
<motion.a
|
||||
href="/contact"
|
||||
className="px-8 py-3 bg-white dark:bg-white text-black dark:text-black rounded-full font-medium shadow-xl"
|
||||
whileHover={{ scale: 1.05, backgroundColor: "#3b82f6", color: "#ffffff" }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
Get IT Support
|
||||
</motion.a>
|
||||
<motion.a
|
||||
href="/contact"
|
||||
className="selection-inverse px-8 py-3 bg-white dark:bg-white text-black dark:text-black rounded-full font-medium shadow-xl"
|
||||
whileHover={{ scale: 1.05, backgroundColor: "#ffffff", color: "#000000" }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
Get IT Support
|
||||
</motion.a>
|
||||
<motion.a
|
||||
href="/services"
|
||||
className="px-8 py-3 bg-white/10 dark:bg-white/10 backdrop-blur-sm border-2 border-white/40 dark:border-white/40 text-white dark:text-white rounded-full font-medium shadow-xl"
|
||||
@@ -189,9 +189,9 @@ const LocationPage: React.FC<LocationPageProps> = ({ data }) => {
|
||||
</section>
|
||||
|
||||
{/* Main Content Section */}
|
||||
<section className="px-6 py-16 relative">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="mb-6">
|
||||
<section className="relative px-6 pt-8 pb-16 md:pt-10">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="mb-4">
|
||||
<Breadcrumb items={[
|
||||
{ label: 'Home', to: '/' },
|
||||
{ label: 'Locations', to: '/locations' },
|
||||
@@ -203,8 +203,8 @@ const LocationPage: React.FC<LocationPageProps> = ({ 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"
|
||||
>
|
||||
<div className="prose prose-lg md:prose-xl dark:prose-invert max-w-none prose-headings:font-display prose-h2:text-3xl prose-h2:mb-6 prose-h2:mt-12 prose-h3:text-2xl prose-p:leading-relaxed prose-li:leading-relaxed prose-a:text-blue-600 dark:prose-a:text-blue-400 prose-a:no-underline hover:prose-a:underline">
|
||||
<div dangerouslySetInnerHTML={{ __html: data.content }} />
|
||||
</div>
|
||||
|
||||
@@ -83,7 +83,7 @@ const LocationsPage: React.FC = () => {
|
||||
Based in Corpus Christi. Serving the Region.
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600 dark:text-gray-300 max-w-2xl mx-auto mb-8 leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
|
||||
216
src/pages/NotFoundPage.tsx
Normal file
216
src/pages/NotFoundPage.tsx
Normal file
@@ -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 (
|
||||
<>
|
||||
<SEO
|
||||
title="404 | Bay Area IT"
|
||||
description="The page you requested could not be found. Return to Bay Area IT to browse services, locations, or contact support."
|
||||
canonicalUrl={canonicalUrl}
|
||||
keywords={['404', 'Bay Area IT', 'Corpus Christi IT support']}
|
||||
/>
|
||||
|
||||
<div className="pt-20 min-h-screen bg-[#0a0a0a] relative overflow-hidden">
|
||||
<div className="absolute inset-0 pointer-events-none">
|
||||
<div className="absolute top-0 left-0 right-0 h-[720px] bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(255,255,255,0.18),rgba(255,255,255,0))]" />
|
||||
<div className="absolute top-24 left-1/2 -translate-x-1/2 w-[40rem] h-[40rem] rounded-full bg-white/[0.06] blur-[140px]" />
|
||||
<div className="absolute bottom-0 right-0 w-[28rem] h-[28rem] rounded-full bg-white/[0.04] blur-[120px]" />
|
||||
</div>
|
||||
|
||||
<section className="relative px-6 py-20 md:py-28 border-b border-white/5">
|
||||
<div className="max-w-7xl mx-auto grid lg:grid-cols-[1.1fr_0.9fr] gap-10 items-start">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 24 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.45 }}
|
||||
>
|
||||
<span className="inline-flex items-center gap-3 px-4 py-2 rounded-full border border-white/10 bg-white/5 text-white/70 text-xs font-bold tracking-[0.24em] uppercase mb-8">
|
||||
<span className="w-2 h-2 rounded-full bg-white shadow-[0_0_16px_rgba(255,255,255,0.85)]" />
|
||||
Route Offline
|
||||
</span>
|
||||
|
||||
<p className="font-display text-7xl md:text-8xl lg:text-[8rem] leading-none tracking-[-0.08em] text-white mb-5">
|
||||
404
|
||||
</p>
|
||||
|
||||
<h1 className="font-display text-4xl md:text-6xl font-bold tracking-tight text-white mb-6">
|
||||
This page is no longer on the network.
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-gray-400 max-w-2xl leading-relaxed mb-10">
|
||||
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.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4 mb-8">
|
||||
<Link
|
||||
to="/"
|
||||
className="px-8 py-4 rounded-full border border-white/20 bg-white/10 text-white font-bold backdrop-blur-sm transition-colors hover:border-white/40 hover:bg-white/15 text-center"
|
||||
>
|
||||
Back to Home
|
||||
</Link>
|
||||
<Link
|
||||
to="/services/business-email-services"
|
||||
className="px-8 py-4 rounded-full border border-white/15 bg-white/5 text-white font-medium backdrop-blur-sm transition-colors hover:border-white/35 hover:bg-white/10 text-center"
|
||||
>
|
||||
View Email Service
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{quickLinks.map((item) => (
|
||||
<Link
|
||||
key={item.to}
|
||||
to={item.to}
|
||||
className="px-4 py-2 rounded-full border border-white/10 bg-black/30 text-sm text-gray-300 transition-colors hover:border-white/30 hover:text-white"
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 24 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.45, delay: 0.08 }}
|
||||
className="rounded-[2rem] border border-white/10 bg-white/5 backdrop-blur-md p-8 md:p-10 shadow-[0_40px_100px_rgba(0,0,0,0.35)]"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-4 mb-8">
|
||||
<div>
|
||||
<p className="text-xs uppercase tracking-[0.24em] text-white/50 mb-2">Diagnostic</p>
|
||||
<h2 className="font-display text-2xl font-bold text-white">Requested Path</h2>
|
||||
</div>
|
||||
<div className="w-12 h-12 rounded-2xl bg-white text-black flex items-center justify-center p-2">
|
||||
<img
|
||||
src="/logo.svg"
|
||||
alt="Bay Area IT logo"
|
||||
className="h-full w-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-2xl border border-white/10 bg-black/30 px-5 py-4 mb-6">
|
||||
<p className="text-xs uppercase tracking-[0.2em] text-white/45 mb-2">Path</p>
|
||||
<p className="text-sm md:text-base text-white break-all">{location.pathname}</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4 mb-8">
|
||||
{diagnostics.map((item) => (
|
||||
<div
|
||||
key={item}
|
||||
className="flex items-start gap-3 rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-4"
|
||||
>
|
||||
<span className="material-symbols-outlined text-white">check_circle</span>
|
||||
<p className="text-sm text-gray-300 leading-relaxed">{item}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="rounded-2xl border border-white/10 bg-white text-black px-5 py-5 selection:bg-black selection:text-white">
|
||||
<p className="text-sm font-bold mb-1 text-black">Direct Help</p>
|
||||
<p className="text-sm text-black/75 mb-4">
|
||||
Call (361) 765-8400 or open the contact page if you need support.
|
||||
</p>
|
||||
<Link
|
||||
to="/contact"
|
||||
className="inline-flex items-center gap-2 text-sm font-semibold text-black hover:text-black/70 transition-colors"
|
||||
>
|
||||
Open Contact Page
|
||||
<span className="material-symbols-outlined text-base text-black">arrow_outward</span>
|
||||
</Link>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="relative px-6 py-20">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="flex items-center gap-3 mb-8">
|
||||
<span className="h-px w-10 bg-white/20" />
|
||||
<p className="text-xs uppercase tracking-[0.24em] text-white/55">Recommended Entry Points</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
{routeCards.map((card, index) => (
|
||||
<motion.div
|
||||
key={card.to}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.4, delay: 0.12 + index * 0.08 }}
|
||||
>
|
||||
<Link
|
||||
to={card.to}
|
||||
className="group h-full block rounded-[1.75rem] border border-white/10 bg-white/5 backdrop-blur-sm p-7 transition-all hover:-translate-y-1 hover:border-white/30 hover:bg-white/[0.08]"
|
||||
>
|
||||
<div className="w-12 h-12 rounded-2xl bg-white/10 border border-white/10 flex items-center justify-center text-white mb-6">
|
||||
<span className="material-symbols-outlined">{card.icon}</span>
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-white mb-3">{card.title}</h3>
|
||||
<p className="text-sm leading-relaxed text-gray-400 mb-6">{card.description}</p>
|
||||
<span className="inline-flex items-center gap-2 text-sm font-semibold text-white">
|
||||
Open Page
|
||||
<span className="material-symbols-outlined text-base transition-transform group-hover:translate-x-1">
|
||||
arrow_forward
|
||||
</span>
|
||||
</span>
|
||||
</Link>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotFoundPage;
|
||||
@@ -121,7 +121,7 @@ const ServicePage: React.FC<ServicePageProps> = ({ data }) => {
|
||||
<section
|
||||
ref={containerRef}
|
||||
onMouseMove={isInteractive ? handleMouseMove : undefined}
|
||||
className="relative min-h-[90vh] flex items-center justify-center overflow-hidden pt-20 group"
|
||||
className="relative flex min-h-[32rem] items-center justify-center overflow-hidden pt-24 pb-14 md:min-h-[36rem] md:pt-28 md:pb-16 lg:min-h-[40rem] lg:pt-32 lg:pb-20 group"
|
||||
>
|
||||
{/* Parallax Background */}
|
||||
<div className="absolute inset-0 z-0 pointer-events-none">
|
||||
@@ -176,8 +176,8 @@ const ServicePage: React.FC<ServicePageProps> = ({ data }) => {
|
||||
<div className="hero-stagger flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||
<motion.a
|
||||
href="/contact"
|
||||
className="px-8 py-3 bg-white dark:bg-white text-black dark:text-black rounded-full font-medium shadow-xl"
|
||||
whileHover={{ scale: 1.05, backgroundColor: "#3b82f6", color: "#ffffff" }}
|
||||
className="selection-inverse px-8 py-3 bg-white dark:bg-white text-black dark:text-black rounded-full font-medium shadow-xl"
|
||||
whileHover={{ scale: 1.05, backgroundColor: "#ffffff", color: "#000000" }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
Get Started
|
||||
@@ -195,9 +195,9 @@ const ServicePage: React.FC<ServicePageProps> = ({ data }) => {
|
||||
</section>
|
||||
|
||||
{/* Main Content Section */}
|
||||
<section className="px-6 py-16 relative">
|
||||
<section className="relative px-6 pt-8 pb-16 md:pt-10">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="mb-6">
|
||||
<div className="mb-4">
|
||||
<Breadcrumb items={[
|
||||
{ label: 'Home', to: '/' },
|
||||
{ label: 'Services', to: '/services' },
|
||||
@@ -209,7 +209,7 @@ const ServicePage: React.FC<ServicePageProps> = ({ 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"
|
||||
>
|
||||
<div className="prose prose-lg md:prose-xl dark:prose-invert max-w-none prose-headings:font-display prose-h2:text-3xl prose-h2:mb-6 prose-h2:mt-12 prose-h3:text-2xl prose-p:leading-relaxed prose-li:leading-relaxed prose-a:text-blue-600 dark:prose-a:text-blue-400 prose-a:no-underline hover:prose-a:underline">
|
||||
<div dangerouslySetInnerHTML={{ __html: data.content }} />
|
||||
|
||||
@@ -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: () =
|
||||
</button>
|
||||
|
||||
{/* Hero Image in Modal */}
|
||||
{service.image && (
|
||||
<div className="w-full h-64 relative">
|
||||
<img
|
||||
src={service.image}
|
||||
alt={service.title}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-[#1a1a1a] to-transparent"></div>
|
||||
</div>
|
||||
)}
|
||||
{service.image && (
|
||||
<div className="w-full h-64 relative">
|
||||
<img
|
||||
src={service.image}
|
||||
alt={service.title}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
fetchPriority="low"
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-[#1a1a1a] to-transparent"></div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="p-8 md:p-12 relative">
|
||||
<div className="flex items-center gap-6 mb-8">
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user