145 lines
4.7 KiB
TypeScript
145 lines
4.7 KiB
TypeScript
"use client";
|
|
|
|
import { useState, FormEvent } from "react";
|
|
import { Loader2, CheckCircle2, AlertCircle } from "lucide-react";
|
|
|
|
type Status = "idle" | "loading" | "success" | "error";
|
|
|
|
export default function ContactForm() {
|
|
const [status, setStatus] = useState<Status>("idle");
|
|
const [errorMessage, setErrorMessage] = useState("");
|
|
|
|
async function handleSubmit(e: FormEvent<HTMLFormElement>) {
|
|
e.preventDefault();
|
|
setStatus("loading");
|
|
setErrorMessage("");
|
|
|
|
const form = e.currentTarget;
|
|
const data = new FormData(form);
|
|
|
|
// Honeypot: если поле заполнено — это бот, тихо считаем успехом.
|
|
if (data.get("website")) {
|
|
setStatus("success");
|
|
form.reset();
|
|
return;
|
|
}
|
|
|
|
const payload = {
|
|
name: String(data.get("name") || ""),
|
|
contact: String(data.get("contact") || ""),
|
|
message: String(data.get("message") || ""),
|
|
};
|
|
|
|
try {
|
|
const res = await fetch("/api/contact", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(payload),
|
|
});
|
|
|
|
const result = await res.json();
|
|
|
|
if (!res.ok || !result.ok) {
|
|
throw new Error(result.error || "Не удалось отправить сообщение");
|
|
}
|
|
|
|
setStatus("success");
|
|
form.reset();
|
|
} catch (err) {
|
|
setStatus("error");
|
|
setErrorMessage(
|
|
err instanceof Error ? err.message : "Не удалось отправить сообщение"
|
|
);
|
|
}
|
|
}
|
|
|
|
if (status === "success") {
|
|
return (
|
|
<div className="flex items-start gap-3 rounded-2xl border border-green-500/25 bg-green-500/[0.06] p-6">
|
|
<CheckCircle2 className="mt-0.5 shrink-0 text-green-400" size={22} />
|
|
<div>
|
|
<p className="font-medium text-ink-100">Сообщение отправлено</p>
|
|
<p className="mt-1 text-sm text-ink-500">
|
|
Мы получили заявку и ответим в ближайшее время.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<form onSubmit={handleSubmit} className="space-y-5">
|
|
{/* Honeypot — скрыто от людей, видно ботам */}
|
|
<input
|
|
type="text"
|
|
name="website"
|
|
tabIndex={-1}
|
|
autoComplete="off"
|
|
className="absolute left-[-9999px] h-0 w-0 opacity-0"
|
|
aria-hidden="true"
|
|
/>
|
|
|
|
<div>
|
|
<label htmlFor="name" className="mb-1.5 block text-sm text-ink-300">
|
|
Имя
|
|
</label>
|
|
<input
|
|
id="name"
|
|
name="name"
|
|
type="text"
|
|
required
|
|
maxLength={100}
|
|
placeholder="Как к вам обращаться"
|
|
className="w-full rounded-xl border border-white/[0.08] bg-base-900/60 px-4 py-3 text-ink-100 placeholder:text-ink-700 focus:border-blue-500/50"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label htmlFor="contact" className="mb-1.5 block text-sm text-ink-300">
|
|
Telegram, WhatsApp или email
|
|
</label>
|
|
<input
|
|
id="contact"
|
|
name="contact"
|
|
type="text"
|
|
required
|
|
maxLength={150}
|
|
placeholder="Как с вами связаться"
|
|
className="w-full rounded-xl border border-white/[0.08] bg-base-900/60 px-4 py-3 text-ink-100 placeholder:text-ink-700 focus:border-blue-500/50"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label htmlFor="message" className="mb-1.5 block text-sm text-ink-300">
|
|
Сообщение
|
|
</label>
|
|
<textarea
|
|
id="message"
|
|
name="message"
|
|
required
|
|
maxLength={2000}
|
|
rows={5}
|
|
placeholder="Расскажите о задаче — направление, сроки, пожелания"
|
|
className="w-full resize-none rounded-xl border border-white/[0.08] bg-base-900/60 px-4 py-3 text-ink-100 placeholder:text-ink-700 focus:border-blue-500/50"
|
|
/>
|
|
</div>
|
|
|
|
{status === "error" && (
|
|
<div className="flex items-start gap-2.5 rounded-xl border border-red-500/25 bg-red-500/[0.06] px-4 py-3 text-sm text-red-300">
|
|
<AlertCircle size={18} className="mt-0.5 shrink-0" />
|
|
{errorMessage}
|
|
</div>
|
|
)}
|
|
|
|
<button
|
|
type="submit"
|
|
disabled={status === "loading"}
|
|
className="inline-flex w-full items-center justify-center gap-2 rounded-full bg-blue-500 px-6 py-3.5 font-medium text-white transition-transform hover:scale-[1.01] disabled:opacity-60 sm:w-auto"
|
|
>
|
|
{status === "loading" && <Loader2 size={18} className="animate-spin" />}
|
|
{status === "loading" ? "Отправляем…" : "Отправить сообщение"}
|
|
</button>
|
|
</form>
|
|
);
|
|
}
|