import { useEffect, useState } from 'react'; import { useNavigate, useSearchParams } from 'react-router-dom'; import { isAxiosError } from 'axios'; import apiClient from '../utils/apiClient'; interface UserData { id: number; username: string; email: string; } const QRLoginPage = () => { const [searchParams] = useSearchParams(); const navigate = useNavigate(); const [status, setStatus] = useState<'loading' | 'confirm' | 'success' | 'error' | 'expired'>('loading'); const [message, setMessage] = useState('Проверка QR-кода...'); const [userData, setUserData] = useState(null); const [remaining, setRemaining] = useState(0); const [requestInfo, setRequestInfo] = useState<{ ip?: string; ua?: string } | null>(null); const code = searchParams.get('code'); useEffect(() => { if (!code) { setStatus('error'); setMessage('Неверный QR-код'); return; } let countdownTimer: ReturnType | null = null; const updateRemaining = async () => { try { const resp = await apiClient.get(`/api/qr-auth/status/${code}`); if (typeof resp.data.expiresIn === 'number') { setRemaining(Math.max(0, Math.ceil(resp.data.expiresIn))); } // Set request info if provided if (resp.data.ipAddress || resp.data.userAgent) { setRequestInfo({ ip: resp.data.ipAddress, ua: resp.data.userAgent }); } if (resp.data.status === 'expired') { setStatus('expired'); setMessage('QR-код истёк'); return false; } return true; } catch (err) { console.error('Ошибка при обновлении remaining:', err); return false; } }; const checkAuth = async () => { try { setStatus('loading'); setMessage('Проверка авторизации...'); // Получаем токен из localStorage const token = localStorage.getItem('access_token'); if (!token) { setStatus('error'); setMessage('Вы не авторизованы. Войдите в аккаунт на телефоне'); return; } try { await apiClient.post('/api/qr-auth/scanning', { code }); } catch (err) { console.log('Не удалось обновить статус на scanning:', err); } // Получаем данные текущего пользователя const userResponse = await apiClient.get('/api/auth/me'); setUserData(userResponse.data.user); setStatus('confirm'); setMessage('Подтвердите вход на новом устройстве'); // Start countdown for confirmation page await updateRemaining(); countdownTimer = setInterval(async () => { await updateRemaining(); // decrease visible counter only if updateRemaining didn't set a new value setRemaining((prev: number) => { if (prev <= 1) { if (countdownTimer) clearInterval(countdownTimer); setStatus('expired'); setMessage('QR-код истёк'); return 0; } return prev - 1; }); }, 1000); } catch (error) { console.error('Ошибка проверки авторизации:', error); if (isAxiosError(error) && error.response?.status === 401) { setStatus('error'); setMessage('Вы не авторизованы. Войдите в аккаунт на телефоне'); } else if (isAxiosError(error) && error.response?.status === 500) { setStatus('error'); setMessage('Серверная ошибка при проверке QR-кода'); } else { setStatus('error'); setMessage('Ошибка проверки авторизации'); } } }; checkAuth(); return () => { if (countdownTimer) clearInterval(countdownTimer); }; }, [code]); const handleConfirm = async () => { try { setStatus('loading'); setMessage('Подтверждение входа...'); const response = await apiClient.post('/api/qr-auth/confirm', { code }); if (response.data.success) { setStatus('success'); setMessage('Вход успешно подтверждён!'); // Перенаправление на главную через 2 секунды setTimeout(() => { window.close(); // Попытка закрыть вкладку если открыта из QR-сканера }, 2000); } } catch (error) { console.error('Ошибка подтверждения:', error); if (isAxiosError(error) && error.response?.status === 401) { setStatus('error'); setMessage('Вы не авторизованы. Войдите в аккаунт на телефоне'); } else if (isAxiosError(error) && (error.response?.status === 404 || error.response?.status === 410)) { setStatus('expired'); setMessage('QR-код истёк или уже использован'); } else if (isAxiosError(error) && error.response?.data && typeof error.response.data === 'object' && 'error' in error.response.data) { const data = error.response.data as { error?: string }; setStatus('error'); setMessage(data.error ?? 'Ошибка подтверждения входа'); } else { setStatus('error'); setMessage('Ошибка подтверждения входа'); } } }; const getIcon = () => { switch (status) { case 'loading': return (
); case 'confirm': return (
); case 'success': return
; case 'expired': return
; case 'error': return
; } }; const getColor = () => { switch (status) { case 'loading': return 'text-blue-600'; case 'confirm': return 'text-gray-800'; case 'success': return 'text-green-600'; case 'expired': return 'text-orange-600'; case 'error': return 'text-red-600'; } }; return (
{getIcon()}

{status === 'loading' && 'Проверка'} {status === 'confirm' && 'Подтвердите вход'} {status === 'success' && 'Успешно!'} {status === 'expired' && 'QR-код истёк'} {status === 'error' && 'Ошибка'}

{status === 'confirm' && userData && (

Войти на новом устройстве как:

{userData.username}

{userData.email}

{/* Device info */}
Детали запроса:
IP: {requestInfo?.ip ?? '—'}
Device: {requestInfo?.ua ?? '—'}
{/* Mobile-friendly confirmation with timer */}

{remaining > 0 ? `Осталось времени: ${Math.floor(remaining / 60)}:${String(remaining % 60).padStart(2, '0')}` : 'QR-код истёк'}

{remaining <= 0 && (
)}
)} {status !== 'confirm' && (

{message}

)} {status === 'success' && (

Вы можете закрыть эту страницу

)} {(status === 'error' || status === 'expired') && ( )}
); }; export default QRLoginPage;