From 259706e21f5f982e84e3292de24fd82b0b47e161 Mon Sep 17 00:00:00 2001 From: nns Date: Mon, 18 May 2026 12:43:53 +0500 Subject: [PATCH] =?UTF-8?q?fix(signup):=20onBlur=20=D0=B2=D0=B0=D0=BB?= =?UTF-8?q?=D0=B8=D0=B4=D0=B0=D1=86=D0=B8=D1=8F=20=D1=87=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D0=B7=20e.target.value,=20=D1=80=D0=B5-=D0=B2=D0=B0=D0=BB?= =?UTF-8?q?=D0=B8=D0=B4=D0=B0=D1=86=D0=B8=D1=8F=20=D0=B2=D0=BC=D0=B5=D1=81?= =?UTF-8?q?=D1=82=D0=BE=20=D1=81=D0=B1=D1=80=D0=BE=D1=81=D0=B0=20=D0=BE?= =?UTF-8?q?=D1=88=D0=B8=D0=B1=D0=BA=D0=B8=20=D0=B2=20onChange?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - onBlur читает e.target.value напрямую из DOM (нет stale closure) - onChange не очищает ошибку, а ре-валидирует (только если ошибка уже показана) - Устраняет баг на мобильном: blur иногда стреляет раньше последнего onChange Co-Authored-By: Claude Sonnet 4.5 --- .../src/components/SignupForm.tsx | 83 +++++++++++++++---- 1 file changed, 65 insertions(+), 18 deletions(-) diff --git a/src/food-market.public/src/components/SignupForm.tsx b/src/food-market.public/src/components/SignupForm.tsx index baa5a9b..64874d6 100644 --- a/src/food-market.public/src/components/SignupForm.tsx +++ b/src/food-market.public/src/components/SignupForm.tsx @@ -2,8 +2,6 @@ import { useState } from 'react' import { validateEmail, validatePassword, validatePhone } from '@/lib/validation' import { PhoneInput } from '@/components/PhoneInput' -// Админский / API endpoint — переключается через PUBLIC_APP_URL на этапе -// билда. Дефолт — admin.food-market.kz (там и API и админ-SPA). const APP_URL = (import.meta.env.PUBLIC_APP_URL as string | undefined) ?? 'https://admin.food-market.kz' const API_URL = APP_URL @@ -81,30 +79,77 @@ export default function SignupForm({ defaultPlan = 'start' }: Props) { {error && (
{error}
)} + - { setEmail(e.target.value); setFieldErrors(p => ({...p, email: undefined})) }} - onBlur={() => { const e = validateEmail(email); setFieldErrors(p => ({...p, email: e ?? undefined})) }} - autoComplete="email" placeholder="name@example.kz" className={inputCls(!!fieldErrors.email)} /> + { + const v = e.target.value + setEmail(v) + // Ре-валидируем только если ошибка уже показана (не мешаем первичному вводу) + setFieldErrors(p => p.email ? {...p, email: validateEmail(v) ?? undefined} : p) + }} + onBlur={(e) => { + // e.target.value — реальное DOM-значение, без stale closure + setFieldErrors(p => ({...p, email: validateEmail(e.target.value) ?? undefined})) + }} + autoComplete="email" + placeholder="name@example.kz" + className={inputCls(!!fieldErrors.email)} + /> + - { setPassword(e.target.value); setFieldErrors(p => ({...p, password: undefined})) }} - onBlur={() => { const e = validatePassword(password); setFieldErrors(p => ({...p, password: e ?? undefined})) }} - autoComplete="new-password" className={inputCls(!!fieldErrors.password)} /> + { + const v = e.target.value + setPassword(v) + setFieldErrors(p => p.password ? {...p, password: validatePassword(v) ?? undefined} : p) + }} + onBlur={(e) => { + setFieldErrors(p => ({...p, password: validatePassword(e.target.value) ?? undefined})) + }} + autoComplete="new-password" + className={inputCls(!!fieldErrors.password)} + /> + - { setOrgName(e.target.value); setFieldErrors(p => ({...p, orgName: undefined})) }} - onBlur={() => { setFieldErrors(p => ({...p, orgName: !orgName.trim() ? 'Название магазина обязательно для заполнения' : undefined})) }} - placeholder="Наименование организации" className={inputCls(!!fieldErrors.orgName)} /> + { + const v = e.target.value + setOrgName(v) + setFieldErrors(p => p.orgName ? {...p, orgName: !v.trim() ? 'Название магазина обязательно для заполнения' : undefined} : p) + }} + onBlur={(e) => { + setFieldErrors(p => ({...p, orgName: !e.target.value.trim() ? 'Название магазина обязательно для заполнения' : undefined})) + }} + placeholder="Наименование организации" + className={inputCls(!!fieldErrors.orgName)} + /> + - { setPhone(v); setFieldErrors(p => ({...p, phone: undefined})) }} - onBlur={() => { const e = validatePhone(phone); setFieldErrors(p => ({...p, phone: e ?? undefined})) }} - required className={inputCls(!!fieldErrors.phone)} /> + { + setPhone(v) + setFieldErrors(p => p.phone ? {...p, phone: validatePhone(v) ?? undefined} : p) + }} + onBlur={(e) => { + // e.target.value — display-формат "+7 7XX XXX XX XX", validatePhone его принимает + setFieldErrors(p => ({...p, phone: validatePhone((e as React.FocusEvent).target.value) ?? undefined})) + }} + required + className={inputCls(!!fieldErrors.phone)} + /> +
Тариф
@@ -121,6 +166,7 @@ export default function SignupForm({ defaultPlan = 'start' }: Props) { ))}
+
{fieldErrors.agree &&

{fieldErrors.agree}

}
+