feat(auth): подтверждение email + актуализация SMTP/Ollama конфигов

- Резенд письма верификации (/auth/resend-verification), модалка на
  фронте с поллингом статуса, страница /verify-email/:token
- SMTP переведён на собственный Postfix (mail.jze9mail.ru, STARTTLS,
  SMTP_TLS_VERIFY) вместо Yandex-заглушки в дефолтах и .env.example
- OLLAMA_URL и модель в worker-gpu синхронизированы с новым GPU-хостом
  (llama3:8b -> qwen2.5:7b, которой раньше не было на сервере)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
jze9
2026-07-23 18:37:22 +05:00
parent 9894fa9320
commit c1cf1ddd2f
11 changed files with 282 additions and 17 deletions

View File

@@ -0,0 +1,83 @@
import { useEffect } from 'react';
import { useParams, useNavigate, Link } from 'react-router-dom';
import { useMutation } from '@tanstack/react-query';
import { CheckCircle2, XCircle, Loader2 } from 'lucide-react';
import { authApi } from '../api/client';
import { useAuthStore } from '../store/auth';
export function VerifyEmail() {
const { token } = useParams<{ token: string }>();
const navigate = useNavigate();
const { updateUser } = useAuthStore();
const verify = useMutation({
mutationFn: () => authApi.verifyEmail(token!),
onSuccess: () => {
updateUser({ is_verified: true });
setTimeout(() => navigate('/cabinet'), 3000);
},
});
useEffect(() => {
if (token) verify.mutate();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [token]);
return (
<div className="max-w-md mx-auto pt-16 text-center">
<div className="bg-white rounded-2xl border border-gray-100 p-10">
{verify.isPending && (
<>
<div className="flex justify-center mb-5">
<Loader2 className="w-14 h-14 text-brand-600 animate-spin" />
</div>
<h1 className="text-xl font-bold text-gray-900 mb-2">Подтверждаем email</h1>
<p className="text-sm text-gray-500">Пожалуйста, подождите</p>
</>
)}
{verify.isSuccess && (
<>
<div className="flex justify-center mb-5">
<div className="p-4 bg-green-50 rounded-full">
<CheckCircle2 className="w-14 h-14 text-green-500" />
</div>
</div>
<h1 className="text-2xl font-bold text-gray-900 mb-2">Email подтверждён!</h1>
<div className="inline-flex items-center gap-2 px-4 py-2 bg-green-50 rounded-xl mb-4">
<CheckCircle2 className="w-4 h-4 text-green-500" />
<span className="text-sm font-medium text-green-700">Email подтверждён</span>
</div>
<p className="text-sm text-gray-500">
Аккаунт активирован. Перенаправляем в личный кабинет
</p>
</>
)}
{verify.isError && (
<>
<div className="flex justify-center mb-5">
<div className="p-4 bg-red-50 rounded-full">
<XCircle className="w-14 h-14 text-red-500" />
</div>
</div>
<h1 className="text-2xl font-bold text-gray-900 mb-2">Ошибка подтверждения</h1>
<div className="inline-flex items-center gap-2 px-4 py-2 bg-red-50 rounded-xl mb-4">
<XCircle className="w-4 h-4 text-red-500" />
<span className="text-sm font-medium text-red-700">Email не подтверждён</span>
</div>
<p className="text-sm text-gray-500 mb-6">
Ссылка недействительна или устарела. Запросите новое письмо в личном кабинете.
</p>
<Link
to="/cabinet"
className="inline-block px-6 py-2.5 bg-brand-600 text-white rounded-xl text-sm font-medium hover:bg-brand-700 transition-colors"
>
В личный кабинет
</Link>
</>
)}
</div>
</div>
);
}