further fixes
This commit is contained in:
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, """);
|
||||
}
|
||||
@@ -1478,6 +1478,7 @@ h3 {
|
||||
display: grid;
|
||||
gap: var(--space-3);
|
||||
max-width: 56rem;
|
||||
margin-inline: auto;
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
@@ -1499,6 +1500,8 @@ h3 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: var(--space-4);
|
||||
max-width: 68rem;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
@media (min-width: 760px) {
|
||||
@@ -2635,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;
|
||||
}
|
||||
|
||||
40
app/page.tsx
40
app/page.tsx
@@ -18,6 +18,7 @@ export default function Page() {
|
||||
const [mailboxes, setMailboxes] = useState(10);
|
||||
const [formErrors, setFormErrors] = useState({ name: "", email: "" });
|
||||
const [formStatus, setFormStatus] = useState("");
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const storedTheme = window.localStorage.getItem("bes-theme");
|
||||
@@ -112,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);
|
||||
@@ -139,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 (
|
||||
@@ -167,6 +200,7 @@ export default function Page() {
|
||||
<AssessmentSection
|
||||
formErrors={formErrors}
|
||||
formStatus={formStatus}
|
||||
isSubmitting={isSubmitting}
|
||||
onAssessmentSubmit={handleAssessmentSubmit}
|
||||
/>
|
||||
</main>
|
||||
|
||||
Reference in New Issue
Block a user