// Auth screens: Email login, Telegram OTP login, Email register const { useState, useEffect } = React; // TG-регистрация: вкл с 2026-06-19. Backend endpoints (/api/auth/telegram/*) // и интеграция с ботом готовы; разделяем register/login через единый флаг. // Чтобы снова спрятать TG-регистрацию — flip в false. const TG_AUTH_ENABLED = true; const AuthPage = ({ mode: initialMode, onLogin, onNavigate, botConfig }) => { const [mode, setMode] = useState(initialMode || 'login'); // Keep internal mode in sync when parent navigates between auth sub-modes // (login → register, register → login with TG open, etc.). Without this, useState's // lazy init means clicks on header "Войти" / "Регистрация" do nothing when // we're already on the auth route. useEffect(() => { setMode(initialMode || 'login'); }, [initialMode]); // Reset transient sub-flow state whenever mode changes (incl. from header nav). useEffect(() => { setError(''); setSuccess(''); setRegStep('form'); setRegCode(''); setOtpSent(false); setOtpCode(''); setNeedsConnect(false); setForgotEmail(''); setForgotStep('email'); setForgotCode(''); setResetNew(''); setResetConfirm(''); setResetDone(false); setActiveMethod(null); }, [mode]); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [name, setName] = useState(''); const [tgId, setTgId] = useState(() => sessionStorage.getItem('auth_tg_phone') || ''); const [otpSent, setOtpSent] = useState(false); const [otpCode, setOtpCode] = useState(''); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [success, setSuccess] = useState(''); // Registration: 'form' (name/email/password) → 'code' (email verification) const [regStep, setRegStep] = useState('form'); const [regCode, setRegCode] = useState(''); const [needsConnect, setNeedsConnect] = useState(false); const [forgotEmail, setForgotEmail] = useState(''); const [forgotStep, setForgotStep] = useState('email'); // 'email' | 'code' const [forgotCode, setForgotCode] = useState(''); const [resetNew, setResetNew] = useState(''); const [resetConfirm, setResetConfirm] = useState(''); const [resetDone, setResetDone] = useState(false); const [activeMethod, setActiveMethod] = useState(null); // null | 'tg' | 'email' | 'sms' function pickMethod(method) { setError(''); setSuccess(''); if (activeMethod === method) { // tap on the same method → collapse setActiveMethod(null); if (method === 'tg') { setOtpSent(false); setOtpCode(''); setNeedsConnect(false); } return; } // switching methods — reset state of the previously-active TG step machine if (activeMethod === 'tg') { setOtpSent(false); setOtpCode(''); setNeedsConnect(false); } setActiveMethod(method); } useEffect(() => { if (tgId) sessionStorage.setItem('auth_tg_phone', tgId); else sessionStorage.removeItem('auth_tg_phone'); }, [tgId]); // Validate phone: strip non-digits/plus; accept +XXXXXXXXXX..XXXXX or 10–11 plain digits. const isValidPhone = (raw) => { const cleaned = (raw || '').replace(/[^\d+]/g, ''); if (/^\+\d{10,15}$/.test(cleaned)) return true; if (/^\d{10,11}$/.test(cleaned)) return true; return false; }; const handleEmailLogin = async () => { if (!email || !password) return setError('Заполните все поля'); setLoading(true); setError(''); try { const data = await api.post('/api/auth/email/login', { email, password }); onLogin(data.access_token); } catch (e) { setError(e.status === 401 ? 'Неверный email или пароль' : (e.message || 'Ошибка входа')); } finally { setLoading(false); } }; const handleRegisterRequest = async () => { if (!email || !password) return setError('Заполните все поля'); if (password.length < 8) return setError('Пароль — минимум 8 символов'); setLoading(true); setError(''); setSuccess(''); try { await api.post('/api/auth/email/register-request', { email, password, first_name: name || null }); setRegStep('code'); setRegCode(''); setSuccess('Код отправлен на ' + email); } catch (e) { if (e.status === 409) setError('Email уже зарегистрирован'); else if (e.status === 429) { const sec = e.retry_after; setError(sec ? `Код уже отправлен. Попробуйте через ${sec} секунд.` : 'Код уже отправлен. Попробуйте позже.'); } else if (e.status === 502) { setError('Не удалось отправить код на email. Попробуйте позже или используйте другой email.'); } else if (e.status === 400) { setError(e.message || 'Неверные данные'); } else { setError(e.message || 'Ошибка регистрации'); } } finally { setLoading(false); } }; const handleRegisterVerify = async () => { if (!regCode || regCode.length < 6) return setError('Введите 6-значный код'); setLoading(true); setError(''); try { const data = await api.post('/api/auth/email/register-verify', { email, code: regCode }); onLogin(data.access_token); } catch (e) { if (e.status === 401) setError('Неверный код'); else if (e.status === 410) setError('Код истёк. Запросите новый.'); else setError(e.message || 'Ошибка проверки кода'); } finally { setLoading(false); } }; const handleResendRegisterCode = async () => { setLoading(true); setError(''); setSuccess(''); try { await api.post('/api/auth/email/register-request', { email, password, first_name: name || null }); setSuccess('Код отправлен на ' + email); } catch (e) { if (e.status === 429) { const sec = e.retry_after; setError(sec ? `Код уже отправлен. Попробуйте через ${sec} секунд.` : 'Код уже отправлен. Попробуйте позже.'); } else if (e.status === 502) { setError('Не удалось отправить код на email. Попробуйте позже.'); } else { setError(e.message || 'Ошибка отправки кода'); } } finally { setLoading(false); } }; const handleRequestOtp = async () => { if (!tgId) return setError('Введите номер телефона'); if (!isValidPhone(tgId)) return setError('Введите номер телефона, например +79001234567'); setLoading(true); setError(''); setSuccess(''); setNeedsConnect(false); try { await api.post('/api/auth/telegram/request-code', { identifier: tgId }); setOtpSent(true); setSuccess('Код отправлен в Telegram'); } catch (e) { // 429 = cooldown; 400 = unknown phone or bot can't reach user; 502 = bot network error. if (e.status === 429) { const sec = e.retry_after; setError(sec ? `Слишком частые запросы. Попробуйте через ${sec} секунд.` : 'Слишком частые запросы. Попробуйте через минуту.'); } else if (e.status === 400) { setNeedsConnect(true); } else if (e.status === 502) { setError('Не удалось отправить код через Telegram. Попробуйте позже.'); } else { setError(e.message || 'Ошибка отправки кода. Попробуйте позже.'); } } finally { setLoading(false); } }; const handleVerifyOtp = async () => { if (!otpCode || otpCode.length < 6) return setError('Введите 6-значный код'); setLoading(true); setError(''); try { const data = await api.post('/api/auth/telegram/verify-code', { identifier: tgId, code: otpCode }); onLogin(data.access_token); } catch (e) { if (e.status === 410) setError('Код истёк — запросите новый'); else if (e.status === 401) setError('Неверный код'); else setError(e.message || 'Ошибка проверки кода'); } finally { setLoading(false); } }; const handleForgotPassword = async () => { if (!forgotEmail) return setError('Введите email'); setLoading(true); setError(''); try { await api.post('/api/auth/email/forgot-password', { email: forgotEmail }); setForgotStep('code'); } catch (e) { if (e.status === 429) { setError('Код уже отправлен, подождите немного перед повторной отправкой'); } else { setError(e.message || 'Ошибка'); } } finally { setLoading(false); } }; const handleResetPassword = async () => { if (!forgotCode || forgotCode.length !== 6) return setError('Введите 6-значный код'); if (!resetNew || resetNew.length < 8) return setError('Пароль — минимум 8 символов'); if (resetNew !== resetConfirm) return setError('Пароли не совпадают'); setLoading(true); setError(''); try { await api.post('/api/auth/email/reset-password', { email: forgotEmail, code: forgotCode, new_password: resetNew, new_password_confirm: resetConfirm, }); setResetDone(true); } catch (e) { if (e.status === 410) { setError('Код истёк — запросите новый'); setForgotStep('email'); setForgotCode(''); } else if (e.status === 401) { setError('Неверный код'); } else { setError(e.message || 'Ошибка'); } } finally { setLoading(false); } }; const logoMark = (
Выберите способ
Код отправлен на {forgotEmail}
{error &&Выберите способ
{error &&