Files
kvarta_project/packages/shared/src/phone.ts
T
nunuchek a2885d180c feat(shared): нормализация белорусского телефона и контракт заявки
normalizeBelarusPhone/phoneError/formatBelarusPhone: любые форматы ввода
→ +375XXXXXXXXX, коды 25/29/33/44/17, подсказки по причине ошибки.
validateLead: типы POST /api/lead, ошибки по полям, honeypot, очистка
касаний. Тесты Vitest.
2026-10-17 15:34:23 +03:00

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)}`;
}