Skip to content

Blueprint: Заявки с форм (Leads)

Когда применять

Любая публичная форма, создающая обращение: «обратный звонок», «оставить заявку», «рассчитать стоимость», «квиз». Один модуль обслуживает все формы сайта — тип формы различается полем source, а не отдельными таблицами.

Не применять для: регистрации пользователей (auth-скаффолдинг), заказов с оплатой (домен заказа), подписки на рассылку (отдельная простая модель).

Состав решения

  • Модель Lead + миграция + фабрика
  • App\Enums\LeadStatus (new, in_progress, done, spam)
  • FormRequest StoreLeadRequest
  • Контроллер LeadController@store (тонкий) либо Livewire-компонент формы
  • App\Services\LeadService — создание + уведомления
  • Notification NewLeadNotification (каналы: database, telegram; ShouldQueue)
  • Filament-ресурс LeadResource (без create — заявки приходят с сайта)
  • Blade-компонент <x-contact-form> с honeypot
  • Feature-тест LeadTest

Схема данных

СтолбецТипИндексCast / примечание
ididPK
namestring
phonestringindexmutator: нормализация к +7XXXXXXXXXX
emailstring nullable
messagetext nullable
sourcestringindexидентификатор формы: callback, quiz-roof, footer
statusenum(LeadStatus)indexcast в PHP Enum, default new
payloadjson nullablecast array; поля квизов/калькуляторов, не раздувать столбцами
utmjson nullablecast array; utm_source и др. из cookie/query
page_urlstring nullableстраница отправки
ipstring nullableдля антиспама/расследований
created_at / updated_attimestampscreated_at indexсортировка в админке

Правила реализации

  • Валидация — в FormRequest: name max:255; phonepropaganistas/laravel-phone (phone:RU) или проектный regex из Constants; message max:2000. Сообщения на русском.
  • Периметр: spatie/laravel-honeypot на компоненте формы + throttle на роуте. Капчу (Turnstile) добавлять только при фактическом спаме.
  • Контроллер ≤10 строк: FormRequest → LeadService::create() → redirect/JSON. UTM и page_url собирает сервис, не контроллер.
  • Уведомления — только через очередь: database (для бейджа в админке) + telegram (канал студии/клиента). Ошибка Telegram не должна ронять сохранение заявки — очередь + failed().
  • Filament: бейдж навигации getNavigationBadge() = count новых; фильтры по status и source; действие «пометить спам»; создание/редактирование полей заявки закрыто, меняется только статус и комментарий менеджера.
  • Ответ формы: redirect back с flash (классика) или JSON для AJAX; страница «спасибо» — если нужна цель метрики.

Пакеты

spatie/laravel-honeypot, laravel-notification-channels/telegram, propaganistas/laravel-phone (опц.). Не подключать CRM-пакеты «на вырост».

Шаги

  1. Миграция + модель + Enum + фабрика (по specs/models).
  2. FormRequest с русскими сообщениями.
  3. LeadService: create → dispatch notification.
  4. Роут POST /lead с ->name('lead.store') + throttle.
  5. Blade-компонент формы с honeypot и CSRF.
  6. Notification с двумя каналами через очередь.
  7. Filament-ресурс: таблица, фильтры, бейдж, статусы.
  8. Тесты: 200-страница с формой, POST валидный → created + notification (Queue::fake), POST невалидный → errors, honeypot-поле заполнено → отказ, throttle → 429.

Чеклист приёмки

  • [ ] POST без CSRF/с honeypot — отклоняется; throttle работает
  • [ ] Телефон нормализуется до сохранения
  • [ ] Notification уходит через очередь; сбой канала не ломает сохранение
  • [ ] В админке: бейдж новых, фильтры, смена статуса; полей заявки не отредактировать
  • [ ] payload/utm — cast array, не строка
  • [ ] Тесты зелёные, Pint чистый

Внутренняя база знаний студии