diff --git a/backend/src/routes/auth.ts b/backend/src/routes/auth.ts index 3389db2..53c78e3 100644 --- a/backend/src/routes/auth.ts +++ b/backend/src/routes/auth.ts @@ -89,11 +89,11 @@ const auth: FastifyPluginAsync = async (fastify) => { // ── POST /api/auth/register ──────────────────────────────────────────────── fastify.post<{ Body: { - hotelName: string - address?: string + hotel_name: string + address: string contact: string email: string - phone?: string + phone: string password: string } }>( @@ -102,20 +102,20 @@ const auth: FastifyPluginAsync = async (fastify) => { schema: { body: { type: 'object', - required: ['hotelName', 'contact', 'email', 'password'], + required: ['hotel_name', 'address', 'contact', 'email', 'phone', 'password'], properties: { - hotelName: { type: 'string', minLength: 2 }, - address: { type: 'string' }, - contact: { type: 'string', minLength: 2 }, - email: { type: 'string' }, - phone: { type: 'string' }, - password: { type: 'string', minLength: 8 }, + hotel_name: { type: 'string', minLength: 2 }, + address: { type: 'string', minLength: 2 }, + contact: { type: 'string', minLength: 2 }, + email: { type: 'string' }, + phone: { type: 'string', minLength: 5 }, + password: { type: 'string', minLength: 8 }, }, }, }, }, async (request, reply) => { - const { hotelName, address, contact, email, phone, password } = request.body + const { hotel_name: hotelName, address, contact, email, phone, password } = request.body if (!/[a-zA-Zа-яА-ЯёЁ]/.test(password)) { return reply.code(400).send({ error: 'Пароль должен содержать хотя бы одну букву' }) diff --git a/src/lib/api.ts b/src/lib/api.ts index eb3253f..273e06a 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -175,13 +175,21 @@ export const api = { register: (data: { hotelName: string - address?: string + address: string contact: string email: string - phone?: string + phone: string password: string }) => - req<{ ok: boolean; message: string }>('POST', '/api/auth/register', data), + // Snake_case вручную — req() конвертирует camelCase→snake_case автоматически + req<{ ok: boolean; message: string }>('POST', '/api/auth/register', { + hotel_name: data.hotelName, + address: data.address, + contact: data.contact, + email: data.email, + phone: data.phone, + password: data.password, + }), forgotPassword: (email: string) => req<{ ok: boolean }>('POST', '/api/auth/forgot-password', { email }), diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx index ef1fb7f..c080e42 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/LoginPage.tsx @@ -285,6 +285,21 @@ function ForgotPasswordForm({ onBack }: { onBack: () => void }) { ) } +// ── Phone mask helper ────────────────────────────────────────────────────────── +function formatPhone(raw: string): string { + const digits = raw.replace(/\D/g, '') + // Normalize: leading 8 or 7 → keep as +7, otherwise prepend 7 + const d = digits.startsWith('8') ? '7' + digits.slice(1) + : digits.startsWith('7') ? digits + : digits.length ? '7' + digits : '' + let result = '+7' + if (d.length > 1) result += ' (' + d.slice(1, Math.min(4, d.length)) + if (d.length >= 4) result += ') ' + d.slice(4, Math.min(7, d.length)) + if (d.length >= 7) result += '-' + d.slice(7, Math.min(9, d.length)) + if (d.length >= 9) result += '-' + d.slice(9, Math.min(11, d.length)) + return result +} + // ── Register form ────────────────────────────────────────────────────────────── function RegisterForm({ onSwitch }: { onSwitch: () => void }) { const [done, setDone] = useState(false) @@ -300,14 +315,24 @@ function RegisterForm({ onSwitch }: { onSwitch: () => void }) { const [loading, setLoading] = useState(false) const [errors, setErrors] = useState>({}) + const handlePhoneChange = (raw: string) => { + // Allow clearing + if (raw === '' || raw === '+') { setPhone(''); return } + setPhone(formatPhone(raw)) + setErrors(prev => ({ ...prev, phone: '' })) + } + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() const err: Record = {} - if (!hotelName.trim()) err.hotelName = 'Введите название отеля' - if (!contact.trim()) err.contact = 'Введите контактное лицо' - if (!email.trim()) err.email = 'Введите email' + if (!hotelName.trim()) err.hotelName = 'Введите название отеля' + if (!address.trim()) err.address = 'Введите адрес отеля' + if (!contact.trim()) err.contact = 'Введите контактное лицо' + if (!email.trim()) err.email = 'Введите email' else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) err.email = 'Некорректный email' - if (password.length < 8) err.password = 'Минимум 8 символов' + const phoneDigits = phone.replace(/\D/g, '') + if (phoneDigits.length < 11) err.phone = 'Введите полный номер телефона' + if (password.length < 8) err.password = 'Минимум 8 символов' else if (!/[a-zA-Zа-яА-ЯёЁ]/.test(password)) err.password = 'Пароль должен содержать хотя бы одну букву' if (!agreed) err.agreed = 'Необходимо принять пользовательское соглашение' setErrors(err) @@ -366,12 +391,13 @@ function RegisterForm({ onSwitch }: { onSwitch: () => void }) { /> - + setAddress(e.target.value)} + onChange={e => { setAddress(e.target.value); setErrors(prev => ({ ...prev, address: '' })) }} /> @@ -396,12 +422,16 @@ function RegisterForm({ onSwitch }: { onSwitch: () => void }) { /> - + setPhone(e.target.value)} + onChange={e => handlePhoneChange(e.target.value)} + onFocus={() => { if (!phone) setPhone('+7 (') }} + onBlur={() => { if (phone === '+7 (' || phone === '+7') setPhone('') }} + inputMode="tel" />