From b8c514013c7c2b34dc1429c670f0383c57c5aac4 Mon Sep 17 00:00:00 2001 From: root Date: Sat, 25 Oct 2025 21:14:15 +0200 Subject: [PATCH] Modernizing + /contact rewire --- web/app/contact/page.tsx | 370 ++++++++++++------ web/app/error.tsx | 43 +- web/app/free/page.tsx | 257 +++++++++++- web/app/globals.css | 11 + web/app/not-found.tsx | 37 +- web/app/page.tsx | 159 +++++++- web/app/services/page.tsx | 141 +++++-- web/components/CTA.tsx | 28 +- web/components/FAQ.tsx | 28 +- web/components/Footer.tsx | 55 ++- web/components/Header.tsx | 30 +- web/components/Hero.tsx | 37 +- web/components/Pricing.tsx | 55 ++- web/components/Process.tsx | 33 +- .../{ServiceCards.tsx => ServiceCard.tsx} | 2 +- web/components/Testimonials.tsx | 11 +- web/lib/site.ts | 4 + web/package.json | 4 +- web/pnpm-lock.yaml | 52 +++ 19 files changed, 1080 insertions(+), 277 deletions(-) rename web/components/{ServiceCards.tsx => ServiceCard.tsx} (96%) diff --git a/web/app/contact/page.tsx b/web/app/contact/page.tsx index 7e91c37..3be9f42 100644 --- a/web/app/contact/page.tsx +++ b/web/app/contact/page.tsx @@ -1,150 +1,264 @@ "use client"; -import { useEffect, useMemo, useState } from "react"; -import { site } from "@/lib/site"; +import { useState } from "react"; +import { useRouter, useSearchParams } from "next/navigation"; +import { motion, AnimatePresence } from "framer-motion"; +import { toast } from "sonner"; -type Status = { state: "idle" | "submitting" | "success" | "error"; message?: string }; +export default function RequestPage() { + const router = useRouter(); + const searchParams = useSearchParams(); + const prefillType = searchParams.get("type") as RequestType | null; -export default function ContactPage() { - const [startedAt, setStartedAt] = useState(""); - const [status, setStatus] = useState({ state: "idle" }); + const [step, setStep] = useState(1); + const [loading, setLoading] = useState(false); + const [form, setForm] = useState({ + type: prefillType || "", + name: "", + email: "", + domain: "", + company: "", + message: "", + hosting: "", + concern: "", + }); - useEffect(() => { - setStartedAt(String(Date.now())); - }, []); + type RequestType = + | "audit" + | "consultation" + | "support" + | "tool" + | "partnership"; - const disabledEarly = useMemo(() => { - if (!startedAt) return true; - const min = Number(process.env.NEXT_PUBLIC_CONTACT_MIN_SUBMIT_SECONDS ?? 3) || 3; - return Date.now() - Number(startedAt) < min * 1000; - }, [startedAt]); + const requestTypes: { id: RequestType; label: string; desc: string; icon: string }[] = [ + { + id: "audit", + label: "Free Audit", + desc: "Request a free website, DNS or performance check.", + icon: "🧠", + }, + { + id: "consultation", + label: "Consultation / Quote", + desc: "Discuss a project, hosting setup, or optimization.", + icon: "⚙️", + }, + { + id: "support", + label: "Technical Support", + desc: "Report an issue or request hands-on help.", + icon: "🛠️", + }, + { + id: "tool", + label: "Tool Follow-Up", + desc: "Continue from one of our free tools or reports.", + icon: "📊", + }, + { + id: "partnership", + label: "Partnership / Collaboration", + desc: "Discuss a potential collaboration or integration.", + icon: "🤝", + }, + ]; - async function onSubmit(e: React.FormEvent) { + const handleChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setForm((f) => ({ ...f, [name]: value })); + }; + + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - setStatus({ state: "submitting" }); - - const form = e.currentTarget; - const formData = new FormData(form); - const payload = Object.fromEntries(formData.entries()); + setLoading(true); try { - const res = await fetch("/api/contact", { + const res = await fetch("/api/request", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload), + body: JSON.stringify(form), }); - const json = (await res.json()) as { ok: boolean; error?: string }; - if (!res.ok || !json.ok) throw new Error(json.error || "Failed"); - setStatus({ state: "success", message: "Thanks! We’ll get back to you shortly." }); - form.reset(); - setStartedAt(String(Date.now())); // reset time trap - } catch { - setStatus({ state: "error", message: "Could not send. Please try again." }); + + if (!res.ok) throw new Error("Submission failed"); + + toast.success("Request submitted successfully!"); + router.push("/request/success"); + } catch (err) { + console.error(err); + toast.error("Something went wrong. Please try again."); + } finally { + setLoading(false); } - } + }; return ( -
-

Contact

-

- Tell us what you want fixed or managed. We’ll confirm scope and start fast. -

- -
- {/* Honeypot (hidden from users & screen readers) */} -
- - -
- - {/* Time trap */} - - -
-
- - -
-
- - -
-
- -
- - -
- -
- -