diff --git a/app/admin/leads/page.tsx b/app/admin/leads/page.tsx index 10849ab..d359887 100644 --- a/app/admin/leads/page.tsx +++ b/app/admin/leads/page.tsx @@ -1,5 +1,6 @@ import { prisma } from "@/lib/prisma"; import LeadStatusSelect from "@/components/lead-status-select"; +import { LeadStatus } from "@prisma/client"; export const dynamic = "force-dynamic"; @@ -8,6 +9,8 @@ type SearchParams = Promise<{ status?: string; }>; +const leadStatuses = Object.values(LeadStatus); + function formatLeadNumber(id: string, createdAt: Date) { const date = new Date(createdAt); const y = date.getFullYear(); @@ -24,7 +27,10 @@ export default async function AdminLeadsPage({ }) { const params = await searchParams; const q = params.q?.trim() || ""; - const status = params.status?.trim() || ""; + const statusParam = params.status?.trim() || ""; + const status = leadStatuses.includes(statusParam as LeadStatus) + ? (statusParam as LeadStatus) + : undefined; const leads = await prisma.lead.findMany({ where: { @@ -39,7 +45,7 @@ export default async function AdminLeadsPage({ ], } : {}, - status ? { status: status as any } : {}, + status ? { status } : {}, ], }, orderBy: { createdAt: "desc" }, diff --git a/app/contacts/page.tsx b/app/contacts/page.tsx index 1f2e97b..2e60ad9 100644 --- a/app/contacts/page.tsx +++ b/app/contacts/page.tsx @@ -1,3 +1,5 @@ +import LeadForm from "@/components/lead-form"; + export default function ContactsPage() { return (
@@ -84,6 +86,18 @@ export default function ContactsPage() { + +
+
+
+ +
+
+
); -} \ No newline at end of file +} diff --git a/app/layout.tsx b/app/layout.tsx index 9a54322..f697134 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -29,10 +29,15 @@ export default function RootLayout({ return ( -
-
- - WorkParking +
+
+ + + WorkParking + + + Smart entry systems +
- +

Интеграция с действующим шлагбаумом

@@ -98,4 +98,4 @@ export default function ServicesPage() { ); -} \ No newline at end of file +} diff --git a/components/barrier-icon.tsx b/components/barrier-icon.tsx new file mode 100644 index 0000000..d35dc4a --- /dev/null +++ b/components/barrier-icon.tsx @@ -0,0 +1,25 @@ +import type { SVGProps } from "react"; + +export default function BarrierIcon(props: SVGProps) { + return ( + + ); +} diff --git a/components/lead-form.tsx b/components/lead-form.tsx index 0c75ccc..4fcee7e 100644 --- a/components/lead-form.tsx +++ b/components/lead-form.tsx @@ -2,8 +2,68 @@ import { useState } from "react"; +type FieldErrors = { + company?: string; + phone?: string; + email?: string; +}; + +function getPhoneDigits(input: string) { + return input.replace(/\D/g, "").slice(0, 11); +} + +function formatRussianPhoneInput(input: string) { + const digits = getPhoneDigits(input); + + if (!digits) { + return ""; + } + + let normalized = digits; + + if (normalized[0] === "8") { + normalized = `7${normalized.slice(1)}`; + } + + if (normalized[0] !== "7") { + normalized = `7${normalized.slice(0, 10)}`; + } + + normalized = normalized.slice(0, 11); + + const country = "+7"; + const part1 = normalized.slice(1, 4); + const part2 = normalized.slice(4, 7); + const part3 = normalized.slice(7, 9); + const part4 = normalized.slice(9, 11); + + let result = country; + + if (part1) { + result += ` (${part1}`; + } + + if (part1.length === 3) { + result += ")"; + } + + if (part2) { + result += ` ${part2}`; + } + + if (part3) { + result += `-${part3}`; + } + + if (part4) { + result += `-${part4}`; + } + + return result; +} + function normalizePhone(input: string) { - const digits = input.replace(/\D/g, ""); + const digits = getPhoneDigits(input); if (digits.length === 11 && (digits.startsWith("7") || digits.startsWith("8"))) { return `7${digits.slice(1)}`; @@ -20,29 +80,70 @@ function isValidEmail(email: string) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); } -export default function LeadForm() { +type LeadFormProps = { + id?: string; + title?: string; + description?: string; + compact?: boolean; +}; + +export default function LeadForm({ + id, + title, + description, + compact = false, +}: LeadFormProps) { const [company, setCompany] = useState(""); const [phone, setPhone] = useState(""); const [email, setEmail] = useState(""); const [message, setMessage] = useState(""); const [isSubmitting, setIsSubmitting] = useState(false); const [resultMessage, setResultMessage] = useState(""); + const [submitError, setSubmitError] = useState(false); + const [errors, setErrors] = useState({}); + + function validateFields() { + const nextErrors: FieldErrors = {}; + const normalizedPhone = normalizePhone(phone); + const trimmedCompany = company.trim(); + const trimmedEmail = email.trim(); + + if (trimmedCompany.length < 2) { + nextErrors.company = "Укажите название объекта или компании"; + } + + if (!normalizedPhone) { + nextErrors.phone = "Введите российский номер в формате +7"; + } + + if (!trimmedEmail) { + nextErrors.email = "Введите email"; + } else if (!isValidEmail(trimmedEmail)) { + nextErrors.email = "Проверьте корректность email"; + } + + return { + nextErrors, + normalizedPhone, + trimmedCompany, + trimmedEmail, + }; + } async function handleSubmit(e: React.FormEvent) { e.preventDefault(); setResultMessage(""); + setSubmitError(false); - const normalizedPhone = normalizePhone(phone); - if (!normalizedPhone) { - setResultMessage("Введите корректный российский телефон"); - return; - } - - if (!isValidEmail(email.trim())) { - setResultMessage("Введите корректный email"); + const { nextErrors, normalizedPhone, trimmedCompany, trimmedEmail } = + validateFields(); + + if (Object.keys(nextErrors).length > 0 || !normalizedPhone) { + setErrors(nextErrors); return; } + setErrors({}); setIsSubmitting(true); try { @@ -52,79 +153,165 @@ export default function LeadForm() { "Content-Type": "application/json", }, body: JSON.stringify({ - company, + company: trimmedCompany, phone: normalizedPhone, - email, - message, + email: trimmedEmail, + message: message.trim(), }), }); const data = await response.json(); if (!response.ok) { + setSubmitError(true); setResultMessage(data.error || "Ошибка отправки"); return; } + setSubmitError(false); setResultMessage("Заявка отправлена. Мы свяжемся с вами."); setCompany(""); setPhone(""); setEmail(""); setMessage(""); } catch { + setSubmitError(true); setResultMessage("Не удалось сохранить заявку"); } finally { setIsSubmitting(false); } } + const fieldClassName = + "w-full rounded-2xl border bg-black/30 px-4 py-3.5 text-white outline-none transition-colors placeholder:text-neutral-500 focus:border-emerald-500 sm:px-5 sm:py-4"; + const labelClassName = "mb-2 block text-sm font-medium text-neutral-200"; + const helperClassName = "mt-2 text-xs text-neutral-500"; + const errorClassName = "mt-2 text-xs text-rose-300"; + return ( -
- setCompany(e.target.value)} - className="w-full rounded-2xl border border-white/10 bg-black/30 px-5 py-4 outline-none placeholder:text-neutral-500 focus:border-emerald-500" - required - /> - - setPhone(e.target.value)} - className="w-full rounded-2xl border border-white/10 bg-black/30 px-5 py-4 outline-none placeholder:text-neutral-500 focus:border-emerald-500" - required - /> - - setEmail(e.target.value)} - className="w-full rounded-2xl border border-white/10 bg-black/30 px-5 py-4 outline-none placeholder:text-neutral-500 focus:border-emerald-500" - required - /> - -