it/web/app/contact/page.tsx
2025-10-25 21:14:15 +02:00

265 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { motion, AnimatePresence } from "framer-motion";
import { toast } from "sonner";
export default function RequestPage() {
const router = useRouter();
const searchParams = useSearchParams();
const prefillType = searchParams.get("type") as RequestType | null;
const [step, setStep] = useState(1);
const [loading, setLoading] = useState(false);
const [form, setForm] = useState({
type: prefillType || "",
name: "",
email: "",
domain: "",
company: "",
message: "",
hosting: "",
concern: "",
});
type RequestType =
| "audit"
| "consultation"
| "support"
| "tool"
| "partnership";
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: "🤝",
},
];
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
const { name, value } = e.target;
setForm((f) => ({ ...f, [name]: value }));
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
try {
const res = await fetch("/api/request", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(form),
});
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 (
<main className="relative isolate min-h-screen bg-gradient-to-b from-neutral-50 via-white to-neutral-100 dark:from-neutral-950 dark:via-neutral-900 dark:to-neutral-950">
<section className="border-b border-neutral-200 dark:border-neutral-800">
<div className="container mx-auto max-w-4xl px-6 py-16 text-center">
<h1 className="text-4xl sm:text-5xl font-bold tracking-tight bg-gradient-to-r from-blue-600 via-purple-600 to-blue-600 bg-clip-text text-transparent">
Start a Request
</h1>
<p className="mt-4 text-neutral-700 dark:text-neutral-300 text-lg">
Choose what youd like to do audits, consultations, or support.
Well guide you through the right steps and get back within one business day.
</p>
</div>
</section>
<section className="container mx-auto max-w-3xl px-6 py-12">
{/* Step 1 Select Request Type */}
<AnimatePresence mode="wait">
{step === 1 && (
<motion.div
key="step1"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
>
<h2 className="text-2xl font-semibold text-neutral-900 dark:text-white mb-6 text-center">
What kind of request do you have?
</h2>
<div className="grid gap-6 sm:grid-cols-2">
{requestTypes.map((t) => (
<button
key={t.id}
onClick={() => {
setForm((f) => ({ ...f, type: t.id }));
setStep(2);
}}
className={`rounded-xl border border-neutral-200 dark:border-neutral-800 p-6 text-left hover:shadow-md transition-all duration-200 ${
form.type === t.id
? "bg-gradient-to-r from-blue-600 via-purple-600 to-blue-600 text-white"
: "bg-white dark:bg-neutral-900 text-neutral-900 dark:text-white"
}`}
>
<div className="text-3xl mb-2">{t.icon}</div>
<h3 className="font-semibold text-lg mb-1">{t.label}</h3>
<p className="text-sm opacity-80">{t.desc}</p>
</button>
))}
</div>
</motion.div>
)}
{/* Step 2 Request Form */}
{step === 2 && (
<motion.form
key="step2"
onSubmit={handleSubmit}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
className="mt-4 bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-2xl shadow-sm p-8"
>
<h2 className="text-xl font-semibold mb-6 text-neutral-900 dark:text-white text-center">
{requestTypes.find((t) => t.id === form.type)?.label}
</h2>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium mb-1 text-neutral-800 dark:text-neutral-200">
Name
</label>
<input
type="text"
name="name"
value={form.name}
onChange={handleChange}
className="w-full rounded-lg border border-neutral-300 dark:border-neutral-700 bg-white/80 dark:bg-neutral-900/80 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
</div>
<div>
<label className="block text-sm font-medium mb-1 text-neutral-800 dark:text-neutral-200">
Email
</label>
<input
type="email"
name="email"
value={form.email}
onChange={handleChange}
required
className="w-full rounded-lg border border-neutral-300 dark:border-neutral-700 bg-white/80 dark:bg-neutral-900/80 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
{/* Show domain and technical fields only for audits / consultations */}
{(form.type === "audit" || form.type === "consultation") && (
<>
<div>
<label className="block text-sm font-medium mb-1 text-neutral-800 dark:text-neutral-200">
Website or Domain
</label>
<input
type="text"
name="domain"
placeholder="example.com"
value={form.domain}
onChange={handleChange}
className="w-full rounded-lg border border-neutral-300 dark:border-neutral-700 bg-white/80 dark:bg-neutral-900/80 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium mb-1 text-neutral-800 dark:text-neutral-200">
Main Concern
</label>
<select
name="concern"
value={form.concern}
onChange={handleChange}
className="w-full rounded-lg border border-neutral-300 dark:border-neutral-700 bg-white/80 dark:bg-neutral-900/80 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="">Select one</option>
<option value="speed">Speed / Performance</option>
<option value="security">Security</option>
<option value="dns">DNS / Email Issues</option>
<option value="hosting">Hosting Migration</option>
<option value="other">Other</option>
</select>
</div>
</>
)}
<div>
<label className="block text-sm font-medium mb-1 text-neutral-800 dark:text-neutral-200">
Message / Details
</label>
<textarea
name="message"
rows={4}
value={form.message}
onChange={handleChange}
placeholder="Describe your request..."
className="w-full rounded-lg border border-neutral-300 dark:border-neutral-700 bg-white/80 dark:bg-neutral-900/80 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
</div>
<div className="mt-8 flex justify-between items-center">
<button
type="button"
onClick={() => setStep(1)}
className="text-sm text-neutral-600 dark:text-neutral-400 hover:underline"
>
Back
</button>
<button
type="submit"
disabled={loading}
className="rounded-lg bg-gradient-to-r from-blue-600 via-purple-600 to-blue-600 text-white font-medium px-6 py-2 hover:opacity-90 transition"
>
{loading ? "Submitting..." : "Submit Request"}
</button>
</div>
</motion.form>
)}
</AnimatePresence>
</section>
</main>
);
}