feat: /:code/:variant URL для вариантов опти + валидация имени варианта

- Роут GET /:code/:variant → редирект на /projects/:uuid (case-insensitive)
- Валидация имени варианта: только URL-безопасные символы [A-Za-z0-9._-]
  (бэкенд validateProjectVariantName + клиентская проверка в обеих формах)
- Подсказки в UI: «Используется в URL: /КОД/Вариант»

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Mikhail Chusavitin
2026-06-23 11:39:50 +03:00
parent 6d4a37df8b
commit b3cab3477b
5 changed files with 59 additions and 13 deletions

View File

@@ -207,9 +207,11 @@
</div>
<div>
<label for="new-variant-value" class="block text-sm font-medium text-gray-700 mb-1">Вариант</label>
<input id="new-variant-value" type="text" placeholder="Например: Lenovo"
<input id="new-variant-value" type="text" placeholder="Например: B200"
pattern="[A-Za-z0-9._-]+"
title="Только буквы, цифры, дефис, точка, подчёркивание"
class="w-full px-3 py-2 border rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<div class="text-xs text-gray-500 mt-1">Оставьте пустым для main нельзя — нужно уникальное значение.</div>
<div class="text-xs text-gray-500 mt-1">Буквы, цифры, дефис, точка, подчёркивание. Используется в URL: /КОД/Вариант.</div>
</div>
</div>
<div class="mt-6 flex justify-end gap-2">
@@ -842,6 +844,10 @@ async function createNewVariant() {
showToast('Укажите вариант', 'error');
return;
}
if (!/^[A-Za-z0-9._-]+$/.test(variant)) {
showToast('Имя варианта содержит недопустимые символы. Разрешены: буквы, цифры, дефис, точка, подчёркивание.', 'error');
return;
}
const payload = {
code: code,
variant: variant,