normalizeBelarusPhone/phoneError/formatBelarusPhone: любые форматы ввода → +375XXXXXXXXX, коды 25/29/33/44/17, подсказки по причине ошибки. validateLead: типы POST /api/lead, ошибки по полям, honeypot, очистка касаний. Тесты Vitest.
73 lines
3.1 KiB
TypeScript
73 lines
3.1 KiB
TypeScript
// Белорусский телефон: +375, код оператора или Минска, 7 цифр.
|
|
// Один и тот же код работает в форме и в api, поэтому подсказки совпадают.
|
|
|
|
export const BELARUS_CODES = ['25', '29', '33', '44', '17'] as const;
|
|
|
|
const SUBSCRIBER_LENGTH = 7;
|
|
|
|
type Parsed =
|
|
| { ok: true; code: string; subscriber: string }
|
|
| { ok: false; reason: 'empty' | 'country' | 'code' | 'length' };
|
|
|
|
function parse(input: string): Parsed {
|
|
const trimmed = input.trim();
|
|
if (trimmed === '') return { ok: false, reason: 'empty' };
|
|
|
|
const hasPlus = trimmed.startsWith('+');
|
|
if (/\+/.test(trimmed.slice(1))) return { ok: false, reason: 'country' };
|
|
const digits = trimmed.replace(/\D/g, '');
|
|
if (digits === '') return { ok: false, reason: 'country' };
|
|
|
|
// Отрезаем префикс страны или старый внутренний «8 0…» / «0…», остаётся код + номер.
|
|
let national: string;
|
|
if (digits.startsWith('375')) national = digits.slice(3);
|
|
else if (hasPlus) return { ok: false, reason: 'country' };
|
|
else if (digits.startsWith('80')) national = digits.slice(2);
|
|
else if (digits.startsWith('0')) national = digits.slice(1);
|
|
else national = digits;
|
|
|
|
const code = national.slice(0, 2);
|
|
const subscriber = national.slice(2);
|
|
|
|
// Без префикса 375: российские «7…» / «8 9xx…» и слишком длинные номера — иностранные.
|
|
if (!digits.startsWith('375') && (/^(7|8[1-9])/.test(digits) || digits.length > 11)) {
|
|
return { ok: false, reason: 'country' };
|
|
}
|
|
if (national.length >= 2 && !(BELARUS_CODES as readonly string[]).includes(code)) {
|
|
return { ok: false, reason: 'code' };
|
|
}
|
|
if (subscriber.length !== SUBSCRIBER_LENGTH) return { ok: false, reason: 'length' };
|
|
return { ok: true, code, subscriber };
|
|
}
|
|
|
|
export const PHONE_ERRORS = {
|
|
empty: 'Укажите телефон — по нему перезвоним',
|
|
country: 'Нужен белорусский номер: +375…',
|
|
code: 'Проверьте код оператора: 25, 29, 33, 44 или 17',
|
|
length: 'После кода нужно 7 цифр',
|
|
} as const;
|
|
|
|
/** Любой формат ввода → `+375XXXXXXXXX` или null. */
|
|
export function normalizeBelarusPhone(input: string): string | null {
|
|
const parsed = parse(input);
|
|
return parsed.ok ? `+375${parsed.code}${parsed.subscriber}` : null;
|
|
}
|
|
|
|
export function isValidBelarusPhone(input: string): boolean {
|
|
return parse(input).ok;
|
|
}
|
|
|
|
/** Текст подсказки под полем или null, если номер валиден. */
|
|
export function phoneError(input: string): string | null {
|
|
const parsed = parse(input);
|
|
return parsed.ok ? null : PHONE_ERRORS[parsed.reason];
|
|
}
|
|
|
|
/** `+375 29 123-45-67` для показа в поле после blur. */
|
|
export function formatBelarusPhone(input: string): string | null {
|
|
const parsed = parse(input);
|
|
if (!parsed.ok) return null;
|
|
const s = parsed.subscriber;
|
|
return `+375 ${parsed.code} ${s.slice(0, 3)}-${s.slice(3, 5)}-${s.slice(5)}`;
|
|
}
|