mirror of
https://github.com/wasrusgen/zov-tech.git
synced 2026-06-03 17:04:48 +00:00
miniapp: phone validation on intro — blocks transition with bad number
- New isValidPhone(raw): checks 11-digit Russian after normalization (8/7/+7/9-prefix) - Intro 'Начать' button now custom click handler instead of data-go - Validates name (non-empty) and phone (Russian format) - Inline .field-error red message under invalid field - .field-hint shows format help under phone input - Haptic 'warning' feedback on invalid submit - Phone is auto-normalized to '+7 900 123-45-67' before transition
This commit is contained in:
parent
0f2635d5f8
commit
5ceffa4f69
@ -148,6 +148,27 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ----- Form errors / hints ----- */
|
||||||
|
.field-error {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
color: #8A3E2A;
|
||||||
|
min-height: 14px;
|
||||||
|
margin-top: 4px;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
.field-error:empty { display: none; }
|
||||||
|
|
||||||
|
.field-hint {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 10px;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
color: var(--muted);
|
||||||
|
margin-top: 4px;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
/* ----- Form basics ----- */
|
/* ----- Form basics ----- */
|
||||||
.field {
|
.field {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@ -178,22 +178,74 @@ const Podbor = (function () {
|
|||||||
<label class="field">
|
<label class="field">
|
||||||
<span class="field-label">Клиент</span>
|
<span class="field-label">Клиент</span>
|
||||||
<input type="text" data-bind="client_name" value="${state.client_name || ""}" placeholder="Например: А. Пестова">
|
<input type="text" data-bind="client_name" value="${state.client_name || ""}" placeholder="Например: А. Пестова">
|
||||||
|
<span class="field-error" id="nameError"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span class="field-label">Телефон</span>
|
<span class="field-label">Телефон</span>
|
||||||
<input type="tel" data-bind="client_phone" value="${state.client_phone || ""}" placeholder="+7 ...">
|
<input type="tel" data-bind="client_phone" value="${state.client_phone || ""}" placeholder="+7 900 123-45-67">
|
||||||
|
<span class="field-hint">Формат: +7, 8, или 10 цифр начиная с 9</span>
|
||||||
|
<span class="field-error" id="phoneError"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="podbor-cta-row">
|
<div class="podbor-cta-row">
|
||||||
<button class="btn-primary" data-go="categories">Начать</button>
|
<button class="btn-primary" id="introNext">Начать</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
`);
|
`);
|
||||||
bindInputs(node);
|
bindInputs(node);
|
||||||
bindNav(node);
|
|
||||||
|
const phoneInput = node.querySelector("input[data-bind='client_phone']");
|
||||||
|
const phoneError = node.querySelector("#phoneError");
|
||||||
|
const nameInput = node.querySelector("input[data-bind='client_name']");
|
||||||
|
const nameError = node.querySelector("#nameError");
|
||||||
|
|
||||||
|
// Валидация на blur — мягкие подсказки
|
||||||
|
phoneInput.addEventListener("blur", () => {
|
||||||
|
const v = phoneInput.value.trim();
|
||||||
|
if (v && !isValidPhone(v)) {
|
||||||
|
phoneError.textContent = "Похоже на неполный номер. Нужно 11 цифр (или 10 с цифры 9)";
|
||||||
|
} else {
|
||||||
|
phoneError.textContent = "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
node.querySelector("#introNext").addEventListener("click", () => {
|
||||||
|
// Имя
|
||||||
|
const name = (state.client_name || "").trim();
|
||||||
|
if (!name) {
|
||||||
|
nameError.textContent = "Укажите имя клиента";
|
||||||
|
nameInput.focus();
|
||||||
|
haptic && haptic("warning");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
nameError.textContent = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Телефон
|
||||||
|
const phone = (state.client_phone || "").trim();
|
||||||
|
if (!phone) {
|
||||||
|
phoneError.textContent = "Укажите телефон клиента";
|
||||||
|
phoneInput.focus();
|
||||||
|
haptic && haptic("warning");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isValidPhone(phone)) {
|
||||||
|
phoneError.textContent = "Неверный формат. Пример: +7 900 123-45-67 или 89001234567";
|
||||||
|
phoneInput.focus();
|
||||||
|
haptic && haptic("warning");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Нормализуем перед переходом
|
||||||
|
const normalized = normalizePhone(phone);
|
||||||
|
if (normalized !== phone) {
|
||||||
|
update({ client_phone: normalized });
|
||||||
|
}
|
||||||
|
go("categories");
|
||||||
|
});
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1539,6 +1591,15 @@ const Podbor = (function () {
|
|||||||
return `+7 ${d.slice(1, 4)} ${d.slice(4, 7)}-${d.slice(7, 9)}-${d.slice(9, 11)}`;
|
return `+7 ${d.slice(1, 4)} ${d.slice(4, 7)}-${d.slice(7, 9)}-${d.slice(9, 11)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Проверка: получится ли валидный РФ-номер из введённого. */
|
||||||
|
function isValidPhone(raw) {
|
||||||
|
if (!raw) return false;
|
||||||
|
let d = raw.replace(/\D/g, "");
|
||||||
|
if (d.length === 11 && d.startsWith("8")) d = "7" + d.slice(1);
|
||||||
|
if (d.length === 10 && d.startsWith("9")) d = "7" + d;
|
||||||
|
return d.length === 11 && d.startsWith("7");
|
||||||
|
}
|
||||||
|
|
||||||
function bindNav(node) {
|
function bindNav(node) {
|
||||||
node.querySelectorAll("[data-go]").forEach(b => {
|
node.querySelectorAll("[data-go]").forEach(b => {
|
||||||
b.addEventListener("click", () => go(b.dataset.go));
|
b.addEventListener("click", () => go(b.dataset.go));
|
||||||
|
|||||||
@ -12,8 +12,8 @@
|
|||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Geist:wght@400;500;600&family=Newsreader:ital,wght@0,400..600;1,400..600&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap">
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Geist:wght@400;500;600&family=Newsreader:ital,wght@0,400..600;1,400..600&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap">
|
||||||
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
||||||
<link rel="stylesheet" href="assets/styles.css?v=20260511i">
|
<link rel="stylesheet" href="assets/styles.css?v=20260511j">
|
||||||
<link rel="stylesheet" href="assets/podbor.css?v=20260511i">
|
<link rel="stylesheet" href="assets/podbor.css?v=20260511j">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main id="app">
|
<main id="app">
|
||||||
@ -21,10 +21,10 @@
|
|||||||
<div class="spinner"></div>
|
<div class="spinner"></div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<script src="assets/icons.js?v=20260511i"></script>
|
<script src="assets/icons.js?v=20260511j"></script>
|
||||||
<script src="assets/podbor.config.js?v=20260511i"></script>
|
<script src="assets/podbor.config.js?v=20260511j"></script>
|
||||||
<script src="assets/podbor.picts.js?v=20260511i"></script>
|
<script src="assets/podbor.picts.js?v=20260511j"></script>
|
||||||
<script src="assets/podbor.js?v=20260511i"></script>
|
<script src="assets/podbor.js?v=20260511j"></script>
|
||||||
<script src="assets/app.js?v=20260511i"></script>
|
<script src="assets/app.js?v=20260511j"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user