feat(signup): телефон обязателен + ФЛК Казахстана (77XXXXXXXXX)
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 1m0s
CI / Web (React + Vite) (push) Successful in 40s
Docker API / Build + push API (push) Successful in 1m20s
Docker Public / Build + push Public (push) Successful in 28s
Docker API / Deploy API on stage (push) Successful in 17s
Docker Public / Deploy Public on stage (push) Successful in 11s

Фронт:
- SignupForm: убрал «(необязательно)» из лейбла, добавил required + autoComplete=tel.
- validation.ts: validatePhone теперь возвращает ошибку при пустом значении и валидирует строго KZ-мобильный (^77\d{9}$); ведущая «8» нормализуется в «7». «79…» (РФ) отвергается.

Бэк:
- AuthSignupController: SignupInput.Phone теперь string (не nullable). Добавлен NormalizeKzPhone — единая нормализация на сервере, защита от обхода фронтового валидатора. На запись в Organization.Phone уходит каноничная форма «+7XXXXXXXXXX».
This commit is contained in:
nurdotnet 2026-05-03 02:52:58 +05:00
parent 36b4fb1b31
commit fd7df631e1
3 changed files with 27 additions and 7 deletions

View file

@ -28,9 +28,22 @@ public AuthSignupController(AppDbContext db, UserManager<User> userMgr)
_db = db; _userMgr = userMgr;
}
public record SignupInput(string Email, string Password, string OrganizationName, string? Phone, string? Plan);
public record SignupInput(string Email, string Password, string OrganizationName, string Phone, string? Plan);
public record SignupResult(Guid OrganizationId, string Email);
/// <summary>Нормализация и проверка телефона Казахстана. Принимаем любое
/// форматирование (пробелы, скобки, +, дефисы), оставляем только цифры,
/// ведущая «8» переписывается в «7». Валидно: ровно 11 цифр, начинается
/// с «77» — мобильный код KZ. «79…» (РФ) и прочие отвергаем.</summary>
private static string? NormalizeKzPhone(string? raw)
{
if (string.IsNullOrWhiteSpace(raw)) return null;
var digits = new string(raw.Where(char.IsDigit).ToArray());
if (digits.Length == 11 && digits[0] == '8') digits = "7" + digits[1..];
if (digits.Length != 11 || !digits.StartsWith("77")) return null;
return "+" + digits;
}
[HttpPost("signup")]
public async Task<ActionResult<SignupResult>> Signup([FromBody] SignupInput input, CancellationToken ct)
{
@ -39,6 +52,9 @@ public async Task<ActionResult<SignupResult>> Signup([FromBody] SignupInput inpu
return BadRequest(new { error = "Email, пароль и название обязательны." });
if (input.Password.Length < 8)
return BadRequest(new { error = "Пароль минимум 8 символов." });
var normalizedPhone = NormalizeKzPhone(input.Phone);
if (normalizedPhone is null)
return BadRequest(new { error = "Введите корректный номер Казахстана. Пример: +7 700 123 45 67" });
var existing = await _userMgr.FindByEmailAsync(input.Email);
if (existing is not null)
@ -63,7 +79,7 @@ public async Task<ActionResult<SignupResult>> Signup([FromBody] SignupInput inpu
Name = input.OrganizationName.Trim(),
CountryCode = "KZ",
DefaultCurrencyId = kzt?.Id,
Phone = string.IsNullOrWhiteSpace(input.Phone) ? null : input.Phone.Trim(),
Phone = normalizedPhone,
Email = input.Email.Trim(),
};
_db.Organizations.Add(org);

View file

@ -92,8 +92,9 @@ export default function SignupForm({ defaultPlan = 'start' }: Props) {
<input type="text" value={orgName} onChange={(e) => setOrgName(e.target.value)}
placeholder="Наименование организации" className={inputCls(!!fieldErrors.orgName)} />
</Field>
<Field label="Телефон (необязательно)" error={fieldErrors.phone}>
<Field label="Телефон" error={fieldErrors.phone}>
<input type="tel" value={phone} onChange={(e) => setPhone(e.target.value)}
required autoComplete="tel" inputMode="tel"
placeholder="+7 700 123 45 67" className={inputCls(!!fieldErrors.phone)} />
</Field>
<div>

View file

@ -30,10 +30,13 @@ export function validatePassword(value: string): string | null {
}
export function validatePhone(value: string): string | null {
if (!value) return null // optional
// +7 700 123 45 67 в любом форматировании, либо 8…
const digits = value.replace(/\D/g, '')
if (!/^[78]\d{10}$/.test(digits)) return 'Введите корректный телефон. Пример: +7 700 123 45 67'
if (!value || value.trim() === '') return 'Телефон обязателен для заполнения'
// Казахстан: +7 7XX XXX XX XX (мобильные коды KZ начинаются с 77,
// 79 — это РФ, исключаем). Принимаем любое форматирование (пробелы,
// скобки, дефисы), нормализуем к 11 цифрам, ведущая «8» = «7».
let digits = value.replace(/\D/g, '')
if (digits.length === 11 && digits.startsWith('8')) digits = '7' + digits.slice(1)
if (!/^77\d{9}$/.test(digits)) return 'Введите корректный номер Казахстана. Пример: +7 700 123 45 67'
return null
}