Appearance
Product Form Validation Design
2026-02-20 | Sprint 2, задача 3+6
Проблема
Форма объявления не валидирует поля на клиенте. Черновик и публикация требуют одинаковых полей. Нет лимитов на длину полей и размер/тип файлов.
Решение
Дефолтные значения
DEFAULT_TITLE = 'Моё объявление'DEFAULT_PRICE = 1- При создании: форма инициализируется дефолтами
- При сохранении черновика: если title пуст — подставляем DEFAULT_TITLE, если price 0 — подставляем DEFAULT_PRICE
- При загрузке фото (ensureDraftForUpload): та же логика подстановки дефолтов
Zod-схемы
Общие лимиты (применяются и к draft, и к publish):
| Поле | Тип | Лимит |
|---|---|---|
| title | string | max 300 |
| description | string | max 5 000 |
| price | integer | 1–100 000 000 |
| oem_number | string | max 50 |
| oem_numbers[] | string[] | max 20 шт, каждый max 50 |
| manufacturer | string | max 100 |
| address | string | max 255 |
publishSchema (дополнительно):
- title: min(1), не равен DEFAULT_TITLE
- price: min(2) (больше дефолтной 1)
draftSchema:
- Всё опционально, лимиты по длине сохраняются
Логика кнопок
- "Сохранить черновик" — всегда активна (убираем :disabled)
- "Опубликовать" — disabled пока
canPublish === false canPublish = title.trim() !== '' && title.trim() !== DEFAULT_TITLE && price > 1
Trim перед отправкой
В buildRequestBody() тримятся: title, description, oem_number, manufacturer, address, каждый элемент oem_numbers[].
Инпут цены
inputmode="numeric", парсим только цифры- Если результат > 100 000 000 — отбрасываем ввод
- Форматирование через
Intl.NumberFormat('ru-RU')
Валидация файлов (useImageUpload)
- MIME: image/jpeg, image/png, image/webp
- Размер: max 10 МБ на файл
- Количество: max 10 фото (серверные + локальные),
PRODUCT_MAX_PHOTOS = 10 - При нарушении — toast с описанием ошибки, файл отклоняется
- При достижении лимита — кнопка «Добавить» disabled + tooltip «Максимум 10 фотографий»
- При ошибке загрузки — иконка ошибки + кнопка retry для повторной попытки
Безопасность
- XSS: Vue экранирует
{{ }}автоматически,v-htmlне используется - Фронтовая валидация — первая линия; бэкенд валидирует повторно
- Файлы: проверка MIME на клиенте + Imagick на сервере
Файлы для изменения
app/entities/product/model/product.schema.ts— Zod-схемыapp/features/product-form/composables/useProductForm.ts— дефолты, trim, валидацияapp/features/product-form/ui/ProductFormPage.vue— UI (кнопки, maxlength, лимит цены)app/features/image-upload/composables/useImageUpload.ts— валидация файловi18n/locales/ru.json— сообщения ошибок валидации