english version update

This commit is contained in:
Georgiy Syralev
2025-12-31 19:59:43 +03:00
parent b799f278a4
commit a2809a705f
57 changed files with 4263 additions and 1333 deletions

View File

@@ -1,4 +1,4 @@
import { useState, useRef, useEffect } from 'react';
import { useState, useRef, useEffect, useCallback } from 'react';
import { Link, useNavigate, useLocation } from 'react-router-dom';
import axios from 'axios';
import { Turnstile } from '@marsidev/react-turnstile';
@@ -6,11 +6,16 @@ import type { TurnstileInstance } from '@marsidev/react-turnstile';
import useAuth from '../context/useAuth';
import { API_URL } from '../config/api';
import { useToast } from '../hooks/useToast';
import { useTranslation } from '../i18n';
import { useLocalePath } from '../middleware';
import { validateEmail } from '../utils/emailValidation';
const RegisterPage = () => {
const { addToast } = useToast();
const [username, setUsername] = useState('');
const [email, setEmail] = useState('');
const [emailError, setEmailError] = useState<string | null>(null);
const [emailSuggestion, setEmailSuggestion] = useState<string | null>(null);
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const [isLoading, setIsLoading] = useState(false);
@@ -19,10 +24,36 @@ const RegisterPage = () => {
const navigate = useNavigate();
const location = useLocation();
const { login } = useAuth();
const { t, locale } = useTranslation();
const localePath = useLocalePath();
const siteKey = import.meta.env.VITE_TURNSTILE_SITE_KEY;
// Обработка OAuth токена из URL
// Email validation on blur
const handleEmailBlur = useCallback(() => {
if (!email.trim()) {
setEmailError(null);
setEmailSuggestion(null);
return;
}
const result = validateEmail(email, locale);
setEmailError(result.isValid ? null : result.error ?? null);
setEmailSuggestion(result.suggestion ?? null);
}, [email, locale]);
// Apply email suggestion
const applySuggestion = useCallback(() => {
if (emailSuggestion) {
const match = emailSuggestion.match(/([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/);
if (match) {
setEmail(match[1]);
setEmailSuggestion(null);
}
}
}, [emailSuggestion]);
// Handle OAuth token from URL
useEffect(() => {
const params = new URLSearchParams(location.search);
const token = params.get('token');
@@ -30,48 +61,57 @@ const RegisterPage = () => {
if (token) {
login(token);
navigate('/dashboard', { replace: true });
navigate(localePath('/dashboard'), { replace: true });
}
if (authError) {
setError('Ошибка авторизации через социальную сеть. Попробуйте снова.');
setError(locale === 'en'
? 'Social login error. Please try again.'
: 'Ошибка авторизации через социальную сеть. Попробуйте снова.');
}
}, [location, login, navigate]);
}, [location, login, navigate, localePath, locale]);
const handleRegister = async (e: React.FormEvent) => {
e.preventDefault();
setError(''); // Очищаем предыдущие ошибки
setError('');
// Validate email before submit
const emailValidation = validateEmail(email, locale);
if (!emailValidation.isValid) {
setEmailError(emailValidation.error ?? t('auth.register.invalidEmail'));
return;
}
if (!turnstileToken) {
setError('Пожалуйста, подтвердите, что вы не робот.');
setError(t('auth.register.captchaRequired'));
return;
}
setIsLoading(true);
try {
await axios.post(`${API_URL}/api/auth/register`, {
await axios.post(`${API_URL}/api/auth/register`, {
username: username,
email: email,
password: password,
turnstileToken: turnstileToken,
});
addToast('Регистрация прошла успешно! Теперь вы можете войти.', 'success');
navigate('/login');
addToast(t('auth.register.success'), 'success');
navigate(localePath('/login'));
} catch (err) {
// Сброс капчи при ошибке
// Reset captcha on error
if (turnstileRef.current) {
turnstileRef.current.reset();
}
setTurnstileToken(null);
if (axios.isAxiosError(err) && err.response) {
const errorMsg = err.response.data.message || 'Неизвестная ошибка регистрации.';
const errorMsg = err.response.data.message || t('auth.register.unknownError');
setError(errorMsg);
} else {
setError('Произошла ошибка сети. Пожалуйста, попробуйте позже.');
setError(t('auth.register.networkError'));
}
} finally {
setIsLoading(false);
@@ -85,27 +125,50 @@ const RegisterPage = () => {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center p-4">
<div className="bg-white p-8 md:p-10 rounded-3xl shadow-2xl w-full max-w-md text-center">
<h1 className="text-3xl font-bold text-gray-800 mb-6">Регистрация</h1>
<h1 className="text-3xl font-bold text-gray-800 mb-6">{t('auth.register.title')}</h1>
<form onSubmit={handleRegister}>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="Имя пользователя"
className="w-full px-5 py-3 mb-4 border border-gray-300 rounded-full focus:outline-none focus:ring-2 focus:ring-ospab-primary"
/>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Электронная почта"
placeholder={t('auth.register.usernamePlaceholder')}
className="w-full px-5 py-3 mb-4 border border-gray-300 rounded-full focus:outline-none focus:ring-2 focus:ring-ospab-primary"
/>
<div className="relative mb-4">
<input
type="email"
value={email}
onChange={(e) => {
setEmail(e.target.value);
setEmailError(null);
setEmailSuggestion(null);
}}
onBlur={handleEmailBlur}
placeholder={t('auth.register.emailPlaceholder')}
className={`w-full px-5 py-3 border rounded-full focus:outline-none focus:ring-2 ${
emailError
? 'border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:ring-ospab-primary'
}`}
/>
{emailError && (
<p className="mt-1 text-sm text-red-500 text-left px-3">{emailError}</p>
)}
{emailSuggestion && (
<button
type="button"
onClick={applySuggestion}
className="mt-1 text-sm text-blue-600 hover:text-blue-800 text-left px-3 cursor-pointer"
>
{emailSuggestion}
</button>
)}
</div>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Пароль"
placeholder={t('auth.register.passwordPlaceholder')}
className="w-full px-5 py-3 mb-6 border border-gray-300 rounded-full focus:outline-none focus:ring-2 focus:ring-ospab-primary"
required
disabled={isLoading}
@@ -119,7 +182,7 @@ const RegisterPage = () => {
onSuccess={(token: string) => setTurnstileToken(token)}
onError={() => {
setTurnstileToken(null);
setError('Ошибка загрузки капчи. Попробуйте обновить страницу.');
setError(t('auth.register.captchaError'));
}}
onExpire={() => setTurnstileToken(null)}
/>
@@ -127,24 +190,24 @@ const RegisterPage = () => {
<button
type="submit"
disabled={isLoading || !turnstileToken}
disabled={isLoading || !turnstileToken || !!emailError}
className="w-full px-5 py-3 rounded-full text-white font-bold transition-colors transform hover:scale-105 bg-ospab-primary hover:bg-ospab-accent disabled:opacity-50 disabled:cursor-not-allowed"
>
{isLoading ? 'Регистрируем...' : 'Зарегистрироваться'}
{isLoading ? t('auth.register.loading') : t('auth.register.submit')}
</button>
</form>
{error && (
<p className="mt-4 text-sm text-red-500">{error}</p>
)}
{/* Социальные сети */}
{/* Social networks */}
<div className="mt-6">
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-300"></div>
</div>
<div className="relative flex justify-center text-sm">
<span className="px-2 bg-white text-gray-500">Или зарегистрироваться через</span>
<span className="px-2 bg-white text-gray-500">{t('auth.register.orRegisterWith')}</span>
</div>
</div>
@@ -185,9 +248,9 @@ const RegisterPage = () => {
</div>
<p className="mt-6 text-gray-600">
Уже есть аккаунт?{' '}
<Link to="/login" className="text-ospab-primary font-bold hover:underline">
Войти
{t('auth.register.haveAccount')}{' '}
<Link to={localePath('/login')} className="text-ospab-primary font-bold hover:underline">
{t('auth.register.loginLink')}
</Link>
</p>
</div>