Compare commits
2 Commits
75ece13f80
...
45422753a3
| Author | SHA1 | Date | |
|---|---|---|---|
| 45422753a3 | |||
| dfd5e744a4 |
6
.env.example
Normal file
6
.env.example
Normal file
@@ -0,0 +1,6 @@
|
||||
# Amazon SES SMTP credentials (NOT IAM access keys — use dedicated SMTP credentials)
|
||||
SES_SMTP_USER=
|
||||
SES_SMTP_PASS=
|
||||
|
||||
# Verified sender email in Amazon SES (must be a verified identity)
|
||||
SES_FROM_EMAIL=support@bayarea-cc.com
|
||||
92
app/api/assessment/route.ts
Normal file
92
app/api/assessment/route.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import nodemailer from "nodemailer";
|
||||
|
||||
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
|
||||
export async function POST(request: Request) {
|
||||
let body: Record<string, unknown>;
|
||||
try {
|
||||
body = await request.json();
|
||||
} catch {
|
||||
return NextResponse.json({ error: "Invalid request body." }, { status: 400 });
|
||||
}
|
||||
|
||||
const name = String(body.name ?? "").trim();
|
||||
const email = String(body.email ?? "").trim();
|
||||
const mailboxes = String(body.mailboxes ?? "").trim();
|
||||
const provider = String(body.provider ?? "").trim();
|
||||
const message = String(body.message ?? "").trim();
|
||||
|
||||
const errors: Record<string, string> = {};
|
||||
if (!name) {
|
||||
errors.name = "Please enter your name.";
|
||||
}
|
||||
if (!email) {
|
||||
errors.email = "Please enter your business email.";
|
||||
} else if (!EMAIL_REGEX.test(email)) {
|
||||
errors.email = "Email address needs to include an @ symbol.";
|
||||
}
|
||||
|
||||
if (Object.keys(errors).length > 0) {
|
||||
return NextResponse.json({ error: "Validation failed.", fields: errors }, { status: 400 });
|
||||
}
|
||||
|
||||
const sesUser = process.env.SES_SMTP_USER;
|
||||
const sesPass = process.env.SES_SMTP_PASS;
|
||||
const fromEmail = process.env.SES_FROM_EMAIL;
|
||||
|
||||
if (!sesUser || !sesPass || !fromEmail) {
|
||||
return NextResponse.json({ error: "Server configuration error." }, { status: 500 });
|
||||
}
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: "email-smtp.us-east-2.amazonaws.com",
|
||||
port: 587,
|
||||
secure: false,
|
||||
auth: {
|
||||
user: sesUser,
|
||||
pass: sesPass,
|
||||
},
|
||||
});
|
||||
|
||||
const textBody = [
|
||||
`Name: ${name}`,
|
||||
`Business Email: ${email}`,
|
||||
`Mailboxes: ${mailboxes || "(not provided)"}`,
|
||||
`Current Provider: ${provider || "(not selected)"}`,
|
||||
`Message: ${message || "(none)"}`,
|
||||
].join("\n");
|
||||
|
||||
const htmlBody = [
|
||||
"<table style='font-family:sans-serif;border-collapse:collapse;'>",
|
||||
`<tr><td style='padding:6px 12px 6px 0;font-weight:700;white-space:nowrap;'>Name</td><td style='padding:6px 0;'>${escapeHtml(name)}</td></tr>`,
|
||||
`<tr><td style='padding:6px 12px 6px 0;font-weight:700;white-space:nowrap;'>Business Email</td><td style='padding:6px 0;'>${escapeHtml(email)}</td></tr>`,
|
||||
`<tr><td style='padding:6px 12px 6px 0;font-weight:700;white-space:nowrap;'>Mailboxes</td><td style='padding:6px 0;'>${escapeHtml(mailboxes || "(not provided)")}</td></tr>`,
|
||||
`<tr><td style='padding:6px 12px 6px 0;font-weight:700;white-space:nowrap;'>Current Provider</td><td style='padding:6px 0;'>${escapeHtml(provider || "(not selected)")}</td></tr>`,
|
||||
`<tr><td style='padding:6px 12px 6px 0;font-weight:700;white-space:nowrap;'>Message</td><td style='padding:6px 0;'>${escapeHtml(message || "(none)")}</td></tr>`,
|
||||
"</table>",
|
||||
].join("\n");
|
||||
|
||||
try {
|
||||
await transporter.sendMail({
|
||||
from: fromEmail,
|
||||
to: "support@bayarea-cc.com",
|
||||
replyTo: email,
|
||||
subject: `New email assessment request — ${name}`,
|
||||
text: textBody,
|
||||
html: htmlBody,
|
||||
});
|
||||
} catch {
|
||||
return NextResponse.json({ error: "Unable to send your request. Please try again or call us." }, { status: 500 });
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
|
||||
function escapeHtml(text: string): string {
|
||||
return text
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
}
|
||||
@@ -542,6 +542,14 @@ h3 {
|
||||
line-height: 1.38;
|
||||
}
|
||||
|
||||
.hero-local-trust {
|
||||
max-width: 32rem;
|
||||
margin: var(--space-2) 0 0;
|
||||
color: var(--blue);
|
||||
font-size: var(--step--1);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.trust-list,
|
||||
.check-stack,
|
||||
.local-strip ul {
|
||||
@@ -1470,6 +1478,7 @@ h3 {
|
||||
display: grid;
|
||||
gap: var(--space-3);
|
||||
max-width: 56rem;
|
||||
margin-inline: auto;
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
@@ -1489,8 +1498,16 @@ h3 {
|
||||
|
||||
.pain-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: var(--space-4);
|
||||
max-width: 68rem;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
@media (min-width: 760px) {
|
||||
.pain-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.pain-grid article {
|
||||
@@ -2621,7 +2638,7 @@ h3 {
|
||||
.pricing-builder {
|
||||
display: grid;
|
||||
gap: var(--space-4);
|
||||
max-width: var(--page-max);
|
||||
max-width: 68rem;
|
||||
margin-inline: auto;
|
||||
padding: 0;
|
||||
}
|
||||
@@ -2789,6 +2806,72 @@ h3 {
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
.offer-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border: 1px solid oklch(67% 0.16 147 / 0.62);
|
||||
border-radius: var(--radius-md);
|
||||
background: oklch(16% 0.06 147 / 0.18);
|
||||
color: var(--green);
|
||||
font-size: var(--step--1);
|
||||
font-weight: 800;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
:root[data-theme="light"] .offer-banner {
|
||||
background: oklch(90% 0.06 147 / 0.32);
|
||||
}
|
||||
|
||||
.offer-banner-icon {
|
||||
flex: 0 0 auto;
|
||||
color: var(--green);
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.price-subtext {
|
||||
margin: 0;
|
||||
color: var(--text-muted);
|
||||
font-size: var(--step--1);
|
||||
}
|
||||
|
||||
.pricing-included-panel {
|
||||
display: grid;
|
||||
gap: var(--space-4);
|
||||
align-content: start;
|
||||
padding: clamp(1.25rem, 2vw, 2rem);
|
||||
border: 1px solid var(--line-soft);
|
||||
border-radius: var(--radius-md);
|
||||
background: oklch(12% 0.034 245 / 0.62);
|
||||
}
|
||||
|
||||
:root[data-theme="light"] .pricing-included-panel {
|
||||
background: oklch(99% 0.006 245 / 0.86);
|
||||
}
|
||||
|
||||
.included-feature-list {
|
||||
display: grid;
|
||||
gap: var(--space-3);
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.included-feature-list li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.included-check {
|
||||
flex: 0 0 auto;
|
||||
color: var(--green);
|
||||
font-weight: 800;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.plan-summary,
|
||||
.pricing-note {
|
||||
margin: 0;
|
||||
@@ -3264,6 +3347,13 @@ textarea::placeholder {
|
||||
font-size: var(--step--1);
|
||||
}
|
||||
|
||||
.form-trust-line {
|
||||
margin: var(--space-2) 0 0;
|
||||
color: var(--text-dim);
|
||||
font-size: var(--step--1);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-status {
|
||||
min-height: 1.5rem;
|
||||
margin: 0;
|
||||
|
||||
107
app/page.tsx
107
app/page.tsx
@@ -4,72 +4,21 @@ import { FormEvent, MouseEvent, useEffect, useState } from "react";
|
||||
import SiteHeader from "../components/SiteHeader";
|
||||
import HeroSection from "../components/HeroSection";
|
||||
import ProblemSection from "../components/ProblemSection";
|
||||
import ProcessSection from "../components/ProcessSection";
|
||||
import DeliverabilitySection from "../components/DeliverabilitySection";
|
||||
import ContinuitySection, { type ContinuityFeature, type FeatureKey } from "../components/ContinuitySection";
|
||||
import MigrationProcessSection from "../components/MigrationProcessSection";
|
||||
import PricingSection, { type Plan } from "../components/PricingSection";
|
||||
import PricingSection from "../components/PricingSection";
|
||||
import FaqSection from "../components/FaqSection";
|
||||
import AssessmentSection from "../components/AssessmentSection";
|
||||
import SiteFooter from "../components/SiteFooter";
|
||||
|
||||
|
||||
type Theme = "dark" | "light";
|
||||
const pricingSummaries: Record<Plan, string> = {
|
||||
hosting:
|
||||
"Core business email hosting with 25 GB mailboxes, custom domain email, and AWS-backed infrastructure.",
|
||||
managed:
|
||||
"Managed setup adds rollout planning, DNS validation, migration coordination, and device handoff checks during the assessment.",
|
||||
};
|
||||
|
||||
const continuityFeatures: Record<FeatureKey, ContinuityFeature> = {
|
||||
buffering: {
|
||||
title: "Inbound buffering",
|
||||
copy: "Incoming mail can be buffered before mailbox delivery during maintenance or provider-side disruption.",
|
||||
proof: [
|
||||
"Supports planned maintenance windows",
|
||||
"Keeps delivery flow observable",
|
||||
"Feeds mailbox delivery after processing",
|
||||
],
|
||||
},
|
||||
sending: {
|
||||
title: "Outbound sending",
|
||||
copy: "Amazon SES gives outbound email an authenticated sending path with reputation tooling and clearer operational visibility.",
|
||||
proof: [
|
||||
"Separates outbound sending from old shared hosting mail",
|
||||
"Uses domain authentication as part of setup",
|
||||
"Makes sending behavior easier to troubleshoot",
|
||||
],
|
||||
},
|
||||
standby: {
|
||||
title: "Standby failover",
|
||||
copy: "A standby environment is part of the continuity plan when primary systems need intervention or provider-side work.",
|
||||
proof: [
|
||||
"Keeps the fallback path visible",
|
||||
"Reduces guesswork during incidents",
|
||||
"Pairs with status checks and local support",
|
||||
],
|
||||
},
|
||||
local: {
|
||||
title: "Local management",
|
||||
copy: "Domain records, migration, mailbox changes, device setup, and troubleshooting stay with a local Corpus Christi team.",
|
||||
proof: [
|
||||
"One support path for DNS and devices",
|
||||
"Migration scope is reviewed before work starts",
|
||||
"Mailbox changes stay tied to the business context",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const [theme, setTheme] = useState<Theme>("dark");
|
||||
const [activeFeature, setActiveFeature] = useState<FeatureKey>("buffering");
|
||||
const [activePlan, setActivePlan] = useState<Plan>("hosting");
|
||||
const [mailboxes, setMailboxes] = useState(25);
|
||||
const [mailboxes, setMailboxes] = useState(10);
|
||||
const [formErrors, setFormErrors] = useState({ name: "", email: "" });
|
||||
const [formStatus, setFormStatus] = useState("");
|
||||
const activeFeatureDetails = continuityFeatures[activeFeature];
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const storedTheme = window.localStorage.getItem("bes-theme");
|
||||
@@ -164,7 +113,7 @@ export default function Page() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleAssessmentSubmit = (event: FormEvent<HTMLFormElement>) => {
|
||||
const handleAssessmentSubmit = async (event: FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
const form = event.currentTarget;
|
||||
const formData = new FormData(form);
|
||||
@@ -191,8 +140,40 @@ export default function Page() {
|
||||
return;
|
||||
}
|
||||
|
||||
setFormStatus("Thanks. We'll review your mailbox count and current provider before we reply.");
|
||||
form.reset();
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/assessment", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
name,
|
||||
email,
|
||||
mailboxes: formData.get("mailboxes") ?? "",
|
||||
provider: formData.get("provider") ?? "",
|
||||
message: formData.get("message") ?? "",
|
||||
}),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
setFormStatus("Thanks. We'll review your mailbox count and current provider before we reply.");
|
||||
form.reset();
|
||||
} else {
|
||||
const data = await response.json().catch(() => null);
|
||||
if (data?.fields) {
|
||||
setFormErrors({
|
||||
name: data.fields.name ?? "",
|
||||
email: data.fields.email ?? "",
|
||||
});
|
||||
} else {
|
||||
setFormStatus(data?.error ?? "Something went wrong. Please try again or call us.");
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
setFormStatus("Network error. Please check your connection and try again.");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -211,25 +192,15 @@ export default function Page() {
|
||||
<main id="main">
|
||||
<HeroSection />
|
||||
<ProblemSection />
|
||||
<ProcessSection />
|
||||
<DeliverabilitySection />
|
||||
<ContinuitySection
|
||||
activeFeature={activeFeature}
|
||||
activeFeatureDetails={activeFeatureDetails}
|
||||
onFeatureChange={setActiveFeature}
|
||||
/>
|
||||
<MigrationProcessSection />
|
||||
<PricingSection
|
||||
activePlan={activePlan}
|
||||
mailboxes={mailboxes}
|
||||
pricingSummaries={pricingSummaries}
|
||||
onPlanChange={setActivePlan}
|
||||
onMailboxesChange={setMailboxes}
|
||||
/>
|
||||
<FaqSection />
|
||||
<AssessmentSection
|
||||
formErrors={formErrors}
|
||||
formStatus={formStatus}
|
||||
isSubmitting={isSubmitting}
|
||||
onAssessmentSubmit={handleAssessmentSubmit}
|
||||
/>
|
||||
</main>
|
||||
|
||||
@@ -3,10 +3,11 @@ import type { FormEventHandler } from "react";
|
||||
type AssessmentSectionProps = {
|
||||
formErrors: { name: string; email: string };
|
||||
formStatus: string;
|
||||
isSubmitting: boolean;
|
||||
onAssessmentSubmit: FormEventHandler<HTMLFormElement>;
|
||||
};
|
||||
|
||||
export default function AssessmentSection({ formErrors, formStatus, onAssessmentSubmit }: AssessmentSectionProps) {
|
||||
export default function AssessmentSection({ formErrors, formStatus, isSubmitting, onAssessmentSubmit }: AssessmentSectionProps) {
|
||||
return (
|
||||
<section className="content-section assessment-section" id="assessment" aria-labelledby="assessment-title">
|
||||
<div className="assessment-bg" aria-hidden="true"></div>
|
||||
@@ -56,17 +57,23 @@ export default function AssessmentSection({ formErrors, formStatus, onAssessment
|
||||
<label htmlFor="message">What should we review?</label>
|
||||
<textarea id="message" name="message" rows={4} placeholder="Domain, mailbox count, migration timing, device setup..."></textarea>
|
||||
</div>
|
||||
<button className="button button-primary button-large" type="submit">
|
||||
Request email assessment
|
||||
<span aria-hidden="true">→</span>
|
||||
<button
|
||||
className="button button-primary button-large"
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
aria-busy={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? "Sending…" : "Request email assessment"}
|
||||
<span aria-hidden="true">{isSubmitting ? "" : "→"}</span>
|
||||
</button>
|
||||
<p className="form-trust-line">We reply within one business day. We don’t share your details.</p>
|
||||
<p className="form-status" role="status" aria-live="polite">{formStatus}</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div className="assessment-copy">
|
||||
<p className="eyebrow">Start here</p>
|
||||
<h2 id="assessment-title">Let’s review your email setup.</h2>
|
||||
<h2 id="assessment-title">Let’s review your email setup.</h2>
|
||||
<p>
|
||||
Tell us about your domain, mailbox count, current provider, and support needs. We will reply with clear next steps for hosting, DNS, migration, and device setup.
|
||||
</p>
|
||||
|
||||
@@ -26,6 +26,10 @@ export default function FaqSection() {
|
||||
<summary>Is this Microsoft 365 or Google Workspace?</summary>
|
||||
<p>No. It is a managed business email hosting service for teams that want professional email, DNS correctness, migration help, and local support without buying a full office suite by default.</p>
|
||||
</details>
|
||||
<details>
|
||||
<summary>What does switching cost?</summary>
|
||||
<p>Setup and migration are included at no extra charge for teams that switch by June 30, 2026. After that, the ongoing cost is $5 per mailbox per month. We confirm the final scope in your assessment before any work begins.</p>
|
||||
</details>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -8,6 +8,9 @@ export default function HeroSection() {
|
||||
<p className="hero-lede">
|
||||
Infrastructure and support from a local team that understands how business gets done.
|
||||
</p>
|
||||
<p className="hero-local-trust">
|
||||
Local team in downtown Corpus Christi — same neighborhood as you.
|
||||
</p>
|
||||
|
||||
<ul className="trust-list" aria-label="Core trust points">
|
||||
<li>
|
||||
|
||||
@@ -1,50 +1,18 @@
|
||||
export type Plan = "hosting" | "managed";
|
||||
|
||||
type PricingSectionProps = {
|
||||
activePlan: Plan;
|
||||
mailboxes: number;
|
||||
pricingSummaries: Record<Plan, string>;
|
||||
onPlanChange: (plan: Plan) => void;
|
||||
onMailboxesChange: (mailboxes: number) => void;
|
||||
};
|
||||
|
||||
export default function PricingSection({ activePlan, mailboxes, pricingSummaries, onPlanChange, onMailboxesChange }: PricingSectionProps) {
|
||||
export default function PricingSection({ mailboxes, onMailboxesChange }: PricingSectionProps) {
|
||||
return (
|
||||
<section className="content-section pricing-detail" id="pricing-detail" aria-labelledby="pricing-title">
|
||||
<div className="section-heading">
|
||||
<p className="eyebrow">Simple mailbox pricing</p>
|
||||
<h2 id="pricing-title">$5 per inbox per month, scoped before you switch.</h2>
|
||||
<p>
|
||||
Pick a mailbox count and plan focus. The base mailbox price is transparent; setup, migration, support scope, minimums, taxes, and add-ons are confirmed during the assessment.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="pricing-builder" data-plan={activePlan} data-mailboxes={mailboxes}>
|
||||
<div className="pricing-builder" data-mailboxes={mailboxes}>
|
||||
<div className="pricing-control-panel">
|
||||
<div>
|
||||
<span className="panel-label">Plan focus</span>
|
||||
<div className="pricing-segmented" data-active={activePlan} role="group" aria-label="Plan focus">
|
||||
<button
|
||||
className={activePlan === "hosting" ? "is-active" : ""}
|
||||
type="button"
|
||||
data-plan="hosting"
|
||||
aria-pressed={activePlan === "hosting"}
|
||||
onClick={() => onPlanChange("hosting")}
|
||||
>
|
||||
Hosting
|
||||
</button>
|
||||
<button
|
||||
className={activePlan === "managed" ? "is-active" : ""}
|
||||
type="button"
|
||||
data-plan="managed"
|
||||
aria-pressed={activePlan === "managed"}
|
||||
onClick={() => onPlanChange("managed")}
|
||||
>
|
||||
Managed setup
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span className="panel-label">Mailbox count</span>
|
||||
<div className="mailbox-options" role="group" aria-label="Estimated mailbox count">
|
||||
@@ -66,66 +34,47 @@ export default function PricingSection({ activePlan, mailboxes, pricingSummaries
|
||||
<div className="price-estimate">
|
||||
<span className="panel-label">Estimated base hosting</span>
|
||||
<p><span className="plan-total">${mailboxes * 5}</span><span>/ month</span></p>
|
||||
<small>Based on <strong className="selected-mailboxes">{mailboxes}</strong> inboxes at $5 each.</small>
|
||||
<small>For teams of 10+ mailboxes.</small>
|
||||
</div>
|
||||
|
||||
<p className="plan-summary">
|
||||
{pricingSummaries[activePlan]}
|
||||
</p>
|
||||
<div className="offer-banner">
|
||||
<span className="offer-banner-icon" aria-hidden="true">★</span>
|
||||
Free migration & setup for teams that switch by June 30, 2026.
|
||||
</div>
|
||||
|
||||
<p className="price-subtext">$5 per mailbox / month. Free migration if you switch by June 30, 2026. Final scope confirmed in your assessment.</p>
|
||||
|
||||
<a className="button button-primary" href="#assessment">Get a mailbox count quote</a>
|
||||
</div>
|
||||
|
||||
<div className="pricing-comparison-panel">
|
||||
<div className="comparison-head">
|
||||
<div>
|
||||
<span className="panel-label">What changes by scope</span>
|
||||
<h3>Hosting first, managed help when the migration needs it.</h3>
|
||||
</div>
|
||||
<span className="status-pill">Operational plan</span>
|
||||
</div>
|
||||
|
||||
<div className="plan-feature-grid" role="table" aria-label="Plan feature comparison">
|
||||
<div className="plan-feature-row table-head" role="row">
|
||||
<span role="columnheader">Feature</span>
|
||||
<span role="columnheader">Hosting</span>
|
||||
<span role="columnheader">Managed setup</span>
|
||||
</div>
|
||||
<div className="plan-feature-row" role="row">
|
||||
<span role="cell">25 GB mailbox</span>
|
||||
<span role="cell">Included</span>
|
||||
<span role="cell">Included</span>
|
||||
</div>
|
||||
<div className="plan-feature-row" role="row">
|
||||
<span role="cell">Custom domain email</span>
|
||||
<span role="cell">Included</span>
|
||||
<span role="cell">Included</span>
|
||||
</div>
|
||||
<div className="plan-feature-row" role="row">
|
||||
<span role="cell">SPF, DKIM, DMARC check</span>
|
||||
<span role="cell">Configured</span>
|
||||
<span role="cell">Configured + validated</span>
|
||||
</div>
|
||||
<div className="plan-feature-row" role="row">
|
||||
<span role="cell">Migration from current provider</span>
|
||||
<span role="cell">Scoped</span>
|
||||
<span role="cell">Planned with handoff</span>
|
||||
</div>
|
||||
<div className="plan-feature-row" role="row">
|
||||
<span role="cell">Outlook, iPhone, iPad setup</span>
|
||||
<span role="cell">Guided</span>
|
||||
<span role="cell">Checked before handoff</span>
|
||||
</div>
|
||||
<div className="plan-feature-row" role="row">
|
||||
<span role="cell">Local support</span>
|
||||
<span role="cell">Available</span>
|
||||
<span role="cell">Priority during rollout</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="pricing-note">
|
||||
No inbox placement guarantees, no zero-downtime promise. The assessment confirms DNS access, provider constraints, migration timing, and device needs before work starts.
|
||||
</p>
|
||||
<div className="pricing-included-panel">
|
||||
<span className="panel-label">What’s included</span>
|
||||
<ul className="included-feature-list">
|
||||
<li>
|
||||
<span className="included-check" aria-hidden="true">✓</span>
|
||||
25 GB mailbox
|
||||
</li>
|
||||
<li>
|
||||
<span className="included-check" aria-hidden="true">✓</span>
|
||||
Custom domain email
|
||||
</li>
|
||||
<li>
|
||||
<span className="included-check" aria-hidden="true">✓</span>
|
||||
SPF, DKIM, DMARC configured
|
||||
</li>
|
||||
<li>
|
||||
<span className="included-check" aria-hidden="true">✓</span>
|
||||
Migration from current provider
|
||||
</li>
|
||||
<li>
|
||||
<span className="included-check" aria-hidden="true">✓</span>
|
||||
Outlook, iPhone, iPad setup
|
||||
</li>
|
||||
<li>
|
||||
<span className="included-check" aria-hidden="true">✓</span>
|
||||
Local Corpus Christi support
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export default function ProblemSection() {
|
||||
return (
|
||||
<section className="content-section problem-section" aria-labelledby="problem-title">
|
||||
<section className="content-section problem-section" id="why" aria-labelledby="problem-title">
|
||||
<div className="section-heading">
|
||||
<p className="eyebrow">Why it matters</p>
|
||||
<h2 id="problem-title">Email problems rarely look technical at first.</h2>
|
||||
@@ -10,17 +10,13 @@ export default function ProblemSection() {
|
||||
</div>
|
||||
<div className="pain-grid">
|
||||
<article>
|
||||
<strong>Free addresses cost trust</strong>
|
||||
<p>Gmail and Yahoo are fine for personal use. A business domain signals that your company is established.</p>
|
||||
<strong>Unprofessional email hurts trust</strong>
|
||||
<p>Free Gmail or Yahoo addresses and unreliable shared-hosting mailboxes both signal to customers that your business is not serious about communication.</p>
|
||||
</article>
|
||||
<article>
|
||||
<strong>DNS mistakes send mail to spam</strong>
|
||||
<p>SPF, DKIM, and DMARC help receiving servers recognize legitimate mail from your domain.</p>
|
||||
</article>
|
||||
<article>
|
||||
<strong>Shared hosting mail breaks quietly</strong>
|
||||
<p>Old hosting mailboxes often hide weak deliverability, limited storage, and unclear support paths.</p>
|
||||
</article>
|
||||
<article>
|
||||
<strong>Support queues slow the fix</strong>
|
||||
<p>When email breaks, a local team can assess the domain, provider, device, and DNS together.</p>
|
||||
|
||||
@@ -26,11 +26,10 @@ export default function SiteFooter() {
|
||||
<nav className="footer-links" aria-label="Footer navigation">
|
||||
<h2>Explore</h2>
|
||||
<ul>
|
||||
<li><a href="#services">Services</a></li>
|
||||
<li><a href="#infrastructure">Infrastructure</a></li>
|
||||
<li><a href="#why">Why email matters</a></li>
|
||||
<li><a href="#pricing-detail">Pricing</a></li>
|
||||
<li><a href="#migration-detail">Migration</a></li>
|
||||
<li><a href="#faq">FAQ</a></li>
|
||||
<li><a href="#assessment">Get assessment</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@@ -47,7 +46,6 @@ export default function SiteFooter() {
|
||||
<div className="footer-bottom">
|
||||
<p>Bay Area Email Services, part of Bay Area IT. All rights reserved.</p>
|
||||
<div>
|
||||
<a href="#assessment">Support</a>
|
||||
<a href="#top" className="back-to-top">
|
||||
Back to top
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
|
||||
@@ -43,12 +43,10 @@ export default function SiteHeader({ menuOpen, theme, onMenuToggle, onThemeToggl
|
||||
</button>
|
||||
|
||||
<nav id="nav-menu" className={`nav-menu${menuOpen ? " is-open" : ""}`} onClick={onNavClick}>
|
||||
<a href="#services">Services</a>
|
||||
<a href="#infrastructure">Infrastructure</a>
|
||||
<a href="#pricing">Pricing</a>
|
||||
<a href="#migration">Migration</a>
|
||||
<a href="#faq">Resources</a>
|
||||
<a href="#assessment">Support</a>
|
||||
<a href="#why">Why email matters</a>
|
||||
<a href="#pricing-detail">Pricing</a>
|
||||
<a href="#faq">FAQ</a>
|
||||
<a href="#assessment">Get assessment</a>
|
||||
</nav>
|
||||
|
||||
<div className="header-actions">
|
||||
|
||||
21
package-lock.json
generated
21
package-lock.json
generated
@@ -9,11 +9,13 @@
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"next": "^15.3.3",
|
||||
"nodemailer": "^6.9.16",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.15.24",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"@types/react": "^19.0.12",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"typescript": "^5.8.3"
|
||||
@@ -648,6 +650,16 @@
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/nodemailer": {
|
||||
"version": "6.4.24",
|
||||
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.24.tgz",
|
||||
"integrity": "sha512-Ww4u0rT9wQNXh4JiQaIwx3QWdcOFXzOjQA2zc+jtFYNmQiT4mIUqcDin51bDFdkzKubFnQCZNK7FIHlPKQ/q9w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz",
|
||||
@@ -781,6 +793,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/nodemailer": {
|
||||
"version": "6.10.1",
|
||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.1.tgz",
|
||||
"integrity": "sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA==",
|
||||
"license": "MIT-0",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
|
||||
@@ -10,11 +10,13 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "^15.3.3",
|
||||
"nodemailer": "^6.9.16",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.15.24",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"@types/react": "^19.0.12",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"typescript": "^5.8.3"
|
||||
|
||||
Reference in New Issue
Block a user