Тема
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 / примечание |
|---|---|---|---|
| id | id | PK | |
| name | string | ||
| phone | string | index | mutator: нормализация к +7XXXXXXXXXX |
| string nullable | |||
| message | text nullable | ||
| source | string | index | идентификатор формы: callback, quiz-roof, footer |
| status | enum(LeadStatus) | index | cast в PHP Enum, default new |
| payload | json nullable | cast array; поля квизов/калькуляторов, не раздувать столбцами | |
| utm | json nullable | cast array; utm_source и др. из cookie/query | |
| page_url | string nullable | страница отправки | |
| ip | string nullable | для антиспама/расследований | |
| created_at / updated_at | timestamps | created_at index | сортировка в админке |
Правила реализации
- Валидация — в FormRequest:
namemax:255;phone—propaganistas/laravel-phone(phone:RU) или проектный regex из Constants;messagemax: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-пакеты «на вырост».
Шаги
- Миграция + модель + Enum + фабрика (по specs/models).
- FormRequest с русскими сообщениями.
- LeadService: create → dispatch notification.
- Роут
POST /leadс->name('lead.store')+ throttle. - Blade-компонент формы с honeypot и CSRF.
- Notification с двумя каналами через очередь.
- Filament-ресурс: таблица, фильтры, бейдж, статусы.
- Тесты: 200-страница с формой, POST валидный → created + notification (Queue::fake), POST невалидный → errors, honeypot-поле заполнено → отказ, throttle → 429.
Чеклист приёмки
- [ ] POST без CSRF/с honeypot — отклоняется; throttle работает
- [ ] Телефон нормализуется до сохранения
- [ ] Notification уходит через очередь; сбой канала не ломает сохранение
- [ ] В админке: бейдж новых, фильтры, смена статуса; полей заявки не отредактировать
- [ ]
payload/utm— cast array, не строка - [ ] Тесты зелёные, Pint чистый