first commit
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user