This commit is contained in:
2026-06-23 13:49:32 -05:00
parent 193b4da576
commit 7aa82f337c
12 changed files with 402 additions and 171 deletions

View File

@@ -0,0 +1,95 @@
"use client";
import { useState } from "react";
export default function ContactForm() {
const [formData, setFormData] = useState({
firstName: "",
lastName: "",
email: "",
phone: "",
serviceType: "",
message: "",
});
const [status, setStatus] = useState<"idle" | "loading" | "success" | "error">("idle");
const [statusMessage, setStatusMessage] = useState("");
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
setFormData((prev) => ({ ...prev, [e.target.name]: e.target.value }));
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setStatus("loading");
setStatusMessage("");
try {
const res = await fetch("/api/contact", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(formData),
});
const data = await res.json();
if (res.ok) {
setStatus("success");
setStatusMessage("Thank you! Your message has been sent. We'll get back to you shortly.");
setFormData({ firstName: "", lastName: "", email: "", phone: "", serviceType: "", message: "" });
} else {
setStatus("error");
setStatusMessage(data.error || "Something went wrong. Please try again.");
}
} catch {
setStatus("error");
setStatusMessage("Network error. Please check your connection and try again.");
}
};
return (
<form className="space-y-6" onSubmit={handleSubmit}>
<div className="grid grid-cols-2 gap-6">
<div className="space-y-2">
<label htmlFor="firstName" className="text-sm font-bold text-gray-700 uppercase tracking-wide">First Name</label>
<input id="firstName" name="firstName" type="text" required value={formData.firstName} onChange={handleChange} className="w-full bg-gray-50 border border-gray-200 p-3 rounded focus:outline-none focus:border-[color:var(--brand)] focus:ring-1 focus:ring-[color:var(--brand)]" />
</div>
<div className="space-y-2">
<label htmlFor="lastName" className="text-sm font-bold text-gray-700 uppercase tracking-wide">Last Name</label>
<input id="lastName" name="lastName" type="text" required value={formData.lastName} onChange={handleChange} className="w-full bg-gray-50 border border-gray-200 p-3 rounded focus:outline-none focus:border-[color:var(--brand)] focus:ring-1 focus:ring-[color:var(--brand)]" />
</div>
</div>
<div className="space-y-2">
<label htmlFor="email" className="text-sm font-bold text-gray-700 uppercase tracking-wide">Email</label>
<input id="email" name="email" type="email" required value={formData.email} onChange={handleChange} className="w-full bg-gray-50 border border-gray-200 p-3 rounded focus:outline-none focus:border-[color:var(--brand)] focus:ring-1 focus:ring-[color:var(--brand)]" />
</div>
<div className="space-y-2">
<label htmlFor="phone" className="text-sm font-bold text-gray-700 uppercase tracking-wide">Phone</label>
<input id="phone" name="phone" type="tel" required value={formData.phone} onChange={handleChange} className="w-full bg-gray-50 border border-gray-200 p-3 rounded focus:outline-none focus:border-[color:var(--brand)] focus:ring-1 focus:ring-[color:var(--brand)]" />
</div>
<div className="space-y-2">
<label htmlFor="serviceType" className="text-sm font-bold text-gray-700 uppercase tracking-wide">Service Type</label>
<select id="serviceType" name="serviceType" required value={formData.serviceType} onChange={handleChange} className="w-full bg-gray-50 border border-gray-200 p-3 rounded focus:outline-none focus:border-[color:var(--brand)] focus:ring-1 focus:ring-[color:var(--brand)]">
<option value="">Select a service type</option>
<option value="commercial">Commercial Project</option>
<option value="residential">Residential Service</option>
<option value="generator">Generator Quote</option>
<option value="other">Other</option>
</select>
</div>
<div className="space-y-2">
<label htmlFor="message" className="text-sm font-bold text-gray-700 uppercase tracking-wide">Message</label>
<textarea id="message" name="message" rows={4} required value={formData.message} onChange={handleChange} className="w-full bg-gray-50 border border-gray-200 p-3 rounded focus:outline-none focus:border-[color:var(--brand)] focus:ring-1 focus:ring-[color:var(--brand)]"></textarea>
</div>
{status === "success" && (
<div className="p-3 bg-green-50 border border-green-200 rounded text-green-700 text-sm">{statusMessage}</div>
)}
{status === "error" && (
<div className="p-3 bg-red-50 border border-red-200 rounded text-red-700 text-sm">{statusMessage}</div>
)}
<button type="submit" disabled={status === "loading"} className="w-full bg-[color:var(--brand)] text-white font-bold uppercase tracking-widest py-4 rounded hover:bg-[color:var(--brand-strong)] transition shadow-lg disabled:opacity-50 disabled:cursor-not-allowed">
{status === "loading" ? "Sending..." : "Send Message"}
</button>
</form>
);
}

View File

@@ -45,7 +45,7 @@ export default function Header() {
href="/contact-us"
className="hidden lg:flex items-center gap-2 rounded bg-[color:var(--brand)] px-5 py-2.5 text-sm font-bold text-white shadow-lg shadow-red-900/20 transition hover:bg-[color:var(--brand-strong)] uppercase tracking-wider"
>
Free Quote
Contact Us
<ArrowRight size={14} strokeWidth={3} />
</Link>
<button
@@ -117,7 +117,7 @@ export default function Header() {
onClick={() => setMobileMenuOpen(false)}
className="flex items-center justify-center gap-2 rounded bg-[color:var(--brand)] px-7 py-4 text-sm font-bold text-white shadow-lg shadow-red-900/20 transition hover:bg-[color:var(--brand-strong)] hover:shadow-red-900/40 uppercase tracking-wider w-full"
>
Get a Quote
Contact Us
<ArrowRight size={16} strokeWidth={3} />
</Link>
</div>

View File

@@ -0,0 +1,74 @@
"use client";
import { useState, useEffect, useCallback } from "react";
import Image from "next/image";
import { X } from "lucide-react";
export default function NewspaperClipping() {
const [open, setOpen] = useState(false);
const close = useCallback(() => setOpen(false), []);
useEffect(() => {
if (!open) return;
const handleKey = (e: KeyboardEvent) => {
if (e.key === "Escape") close();
};
document.addEventListener("keydown", handleKey);
document.body.style.overflow = "hidden";
return () => {
document.removeEventListener("keydown", handleKey);
document.body.style.overflow = "";
};
}, [open, close]);
return (
<>
<div
className="aspect-[4/3] rounded-lg overflow-hidden bg-gray-100 shadow-xl border-l-8 border-[color:var(--brand)] cursor-pointer group relative"
onClick={() => setOpen(true)}
>
<Image
src="/images/newspaper.webp"
alt="Budd Electric Co. featured in the local newspaper"
fill
sizes="(max-width: 1024px) 100vw, 50vw"
className="object-contain p-3 group-hover:scale-105 transition-transform duration-300"
loading="lazy"
/>
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/10 transition-colors flex items-center justify-center">
<span className="text-white text-sm font-bold opacity-0 group-hover:opacity-100 transition-opacity bg-black/60 px-4 py-2 rounded">Click to enlarge</span>
</div>
</div>
<p className="mt-3 text-center text-sm text-gray-400 italic">Featured in the local press</p>
{open && (
<div
className="fixed inset-0 z-50 bg-black/85 flex items-center justify-center p-4 md:p-8"
onClick={close}
>
<button
onClick={close}
className="absolute top-4 right-4 text-white/80 hover:text-white transition-colors z-10"
aria-label="Close"
>
<X size={36} strokeWidth={2} />
</button>
<div
className="relative max-w-5xl w-full max-h-[90vh]"
onClick={(e) => e.stopPropagation()}
>
<Image
src="/images/newspaper.webp"
alt="Budd Electric Co. featured in the local newspaper"
width={1600}
height={1200}
className="object-contain w-full h-full max-h-[90vh]"
unoptimized
/>
</div>
</div>
)}
</>
);
}