import { useState, useEffect, useCallback, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; import { useLanguage } from '../i18n/LanguageContext'; import { fonts, radius } from '../styles/theme'; import { getNextTypingSentence, checkTypingSentence, getTypingHint, getTypingProgress, getTTS } from '../api'; interface TypingSentence { completed: boolean; sentence_id: number; translation: string; level: string; } const TypingPage: React.FC = () => { const navigate = useNavigate(); const { t } = useLanguage(); const [sentence, setSentence] = useState(null); const [inputValue, setInputValue] = useState(''); const [loading, setLoading] = useState(true); const [isCorrect, setIsCorrect] = useState(null); const [showAnswer, setShowAnswer] = useState(false); const [correctAnswer, setCorrectAnswer] = useState(''); const [totalCompleted, setTotalCompleted] = useState(0); const [totalSentences, setTotalSentences] = useState(0); const [isPlaying, setIsPlaying] = useState(false); const audioRef = useRef(null); const brandColor = '#2ECC71'; const textColor = '#1A1A2E'; const subColor = '#6B7280'; const bgColor = '#F4F5F7'; const cardBg = 'rgba(255,255,255,0.55)'; const glass = (extra: React.CSSProperties = {}): React.CSSProperties => ({ background: cardBg, backdropFilter: 'blur(24px) saturate(180%)', WebkitBackdropFilter: 'blur(24px) saturate(180%)', border: '1px solid rgba(255,255,255,0.7)', boxShadow: '0 8px 32px rgba(31,38,73,0.10), inset 0 1px 0 rgba(255,255,255,0.85)', ...extra, }); const fetchSentence = useCallback(() => { setLoading(true); setIsCorrect(null); setShowAnswer(false); setInputValue(''); setCorrectAnswer(''); setIsPlaying(false); if (audioRef.current) { audioRef.current.pause(); audioRef.current = null; } getNextTypingSentence() .then((data: any) => { if (data.completed) { setSentence({ completed: true, sentence_id: 0, translation: '', level: '' }); } else { setSentence(data); } }) .catch(() => { navigate('/menu'); }) .finally(() => setLoading(false)); getTypingProgress() .then((data: any) => { setTotalCompleted(data.completed || 0); setTotalSentences(data.total || 0); }) .catch(() => {}); }, [navigate]); useEffect(() => { fetchSentence(); return () => { if (audioRef.current) { audioRef.current.pause(); audioRef.current = null; } if ('speechSynthesis' in window) { window.speechSynthesis.cancel(); } }; }, [fetchSentence]); const handleCheck = async () => { if (!sentence || !inputValue.trim()) return; try { const data = await checkTypingSentence(sentence.sentence_id, inputValue); setIsCorrect(data.correct); setCorrectAnswer(data.correct_answer); if (data.correct) { setTotalCompleted(prev => prev + 1); setTimeout(() => { fetchSentence(); }, 1500); } } catch (e) { console.error('Error checking sentence:', e); } }; const handleShowAnswer = async () => { if (!sentence) return; try { const data = await getTypingHint(sentence.sentence_id); setCorrectAnswer(data.correct_answer); setShowAnswer(true); } catch (e) { console.error('Error getting hint:', e); } }; const playAnswer = async () => { if (!correctAnswer) return; if (audioRef.current) { audioRef.current.pause(); audioRef.current = null; } if ('speechSynthesis' in window) { window.speechSynthesis.cancel(); } setIsPlaying(true); try { const audioBlob = await getTTS(correctAnswer, 'male'); const audioUrl = URL.createObjectURL(audioBlob); const audio = new Audio(audioUrl); audioRef.current = audio; audio.onended = () => setIsPlaying(false); audio.onerror = () => setIsPlaying(false); await audio.play(); } catch (e) { console.error('Error playing TTS:', e); setIsPlaying(false); } }; if (loading) { return (
{t('loading')}
); } return (
{/* Модалка с ответом */} {showAnswer && correctAnswer && (
{ if (e.target === e.currentTarget) setShowAnswer(false); }} >
🇵🇱

{t('typingShowAnswer')}:

{correctAnswer}
)} {/* Заголовок */}

⌨️ {t('typingTitle')}

{/* Прогресс */}
{t('completed')}: {totalCompleted} / {totalSentences}
{sentence?.completed ? (
🎉
{t('allSentencesCompleted')}
) : ( <> {/* Перевод */}
{t('typingInstructions')}
{sentence?.translation}
{/* Поле ввода */} setInputValue(e.target.value)} placeholder={t('typingPlaceholder')} disabled={isCorrect === true} style={{ width: '100%', padding: '16px', borderRadius: radius.lg, border: isCorrect === false ? `2px solid #E74C3C` : isCorrect === true ? `2px solid #2ECC71` : '1px solid rgba(255,255,255,0.7)', background: isCorrect === true ? 'rgba(46,204,113,0.1)' : cardBg, color: textColor, fontSize: '18px', fontWeight: 600, fontFamily: fonts.main, outline: 'none', textAlign: 'center', transition: 'all 0.2s ease', boxShadow: isCorrect === false ? '0 0 15px rgba(231,76,60,0.3)' : '0 2px 8px rgba(31,38,73,0.06)', animation: isCorrect === false ? 'shake 0.4s ease' : 'none', }} /> {/* Статус */} {isCorrect === true && (
✅ {t('typingCorrect')}
)} {isCorrect === false && (
❌ {t('typingIncorrect')}
)} {/* Кнопки */}
{isCorrect === true ? ( ) : ( <> )}
)}
); }; export default TypingPage;