diff --git a/app/resources/js/utils/phone.ts b/app/resources/js/utils/phone.ts new file mode 100644 index 00000000..6efa2561 --- /dev/null +++ b/app/resources/js/utils/phone.ts @@ -0,0 +1,32 @@ +/** + * Утилиты телефона РФ для формы регистрации. + * Хранение/отправка — нормализованные цифры 7XXXXXXXXXX (как на backend). + * Отображение — маска +7 (XXX) XXX-XX-XX. + */ + +/** Извлекает цифры и нормализует к 7XXXXXXXXXX (макс. 11 цифр). */ +export function phoneDigits(raw: string): string { + let d = raw.replace(/\D+/g, ''); + if (d.length === 0) return ''; + if (d[0] === '8') d = '7' + d.slice(1); + if (d[0] !== '7') d = '7' + d; + return d.slice(0, 11); +} + +/** Прогрессивная маска: '' → '', '7912' → '+7 (912', полный → '+7 (NNN) NNN-NN-NN'. */ +export function formatPhone(raw: string): string { + const d = phoneDigits(raw); + if (d === '') return ''; + const rest = d.slice(1); // до 10 цифр после ведущей 7 + let out = '+7'; + if (rest.length > 0) out += ' (' + rest.slice(0, 3); + if (rest.length > 3) out += ') ' + rest.slice(3, 6); + if (rest.length > 6) out += '-' + rest.slice(6, 8); + if (rest.length > 8) out += '-' + rest.slice(8, 10); + return out; +} + +/** Полный валидный RU-номер? */ +export function isValidPhone(raw: string): boolean { + return /^7\d{10}$/.test(phoneDigits(raw)); +} diff --git a/app/tests/Frontend/phone.spec.ts b/app/tests/Frontend/phone.spec.ts new file mode 100644 index 00000000..0745ed08 --- /dev/null +++ b/app/tests/Frontend/phone.spec.ts @@ -0,0 +1,26 @@ +import { describe, it, expect } from 'vitest'; +import { phoneDigits, formatPhone, isValidPhone } from '../../resources/js/utils/phone'; + +// Phone numbers below are test fixtures, not real PII. gitleaks:allow +describe('phone utils', () => { + it('phoneDigits нормализует к 7XXXXXXXXXX', () => { + expect(phoneDigits('+7 (912) 345-67-89')).toBe('79123456789'); // gitleaks:allow + expect(phoneDigits('8 912 345 67 89')).toBe('79123456789'); // gitleaks:allow + expect(phoneDigits('9123456789')).toBe('79123456789'); // gitleaks:allow + expect(phoneDigits('')).toBe(''); + }); + + it('formatPhone строит маску прогрессивно', () => { + expect(formatPhone('')).toBe(''); + expect(formatPhone('7912')).toBe('+7 (912'); + expect(formatPhone('79123456789')).toBe('+7 (912) 345-67-89'); // gitleaks:allow + // лишние цифры обрезаются до 11 + expect(formatPhone('791234567890000')).toBe('+7 (912) 345-67-89'); // gitleaks:allow + }); + + it('isValidPhone true только для полного 7+10', () => { + expect(isValidPhone('+7 (912) 345-67-89')).toBe(true); // gitleaks:allow + expect(isValidPhone('7912345')).toBe(false); + expect(isValidPhone('')).toBe(false); + }); +}); diff --git a/docs/observer/STATUS.md b/docs/observer/STATUS.md index bb6f0c18..973a4e09 100644 --- a/docs/observer/STATUS.md +++ b/docs/observer/STATUS.md @@ -1,6 +1,6 @@ # Brain Status (auto-generated) -Last updated: 2026-05-21T08:42:35.722Z +Last updated: 2026-05-21T16:21:23.974Z | Контролёр | Состояние | Детали | |---|---|---| @@ -8,12 +8,12 @@ Last updated: 2026-05-21T08:42:35.722Z | C2 Cross-ref consistency | ✅ | [cross-ref-checker] OK — 0 drift in 4 files | | C3 Observer-of-observer | ✅ | [observer-of-observer] OK — last read 0 week(s) ago | | C4 Сигнальный статус | ✅ | This file (self-reference) | -| C5 Observer-coverage | ✅ | 71 episode(s) this month · Stop-hook + post-commit OK | +| C5 Observer-coverage | ✅ | 90 episode(s) this month · Stop-hook + post-commit OK | | C6 Chain map sync | ✅ | [chain-map-checker] OK — 14 chains in sync | ## Метрики (информационные, не алерты) -- Observer evidence: 71 episodes this month, 0 observer_error markers, 52 PII matches before filter +- Observer evidence: 90 episodes this month, 0 observer_error markers, 108 PII matches before filter - Legacy v1 episodes (not in factor analysis): 5 - Last /brain-retro: 2 day(s) ago - Использование узлов: см. `/brain-retro` (раз в спринт). **Неиспользованные узлы — не проблема** (capability-readiness; см. memory `feedback_brain_unused_tools_not_problem` — outside-repo memory store).