Add docker

This commit is contained in:
2026-01-10 17:51:08 +03:00
parent 62d33acde9
commit 8167c77a27
36 changed files with 1351 additions and 117 deletions
+8 -3
View File
@@ -21,7 +21,7 @@ export default function LoginForm() {
setIsLoading(true);
try {
const data = await authService.login({ username, password });
await authService.login({ username, password });
const user = await authService.getCurrentUser();
setUser(user);
navigate('/chat');
@@ -55,9 +55,14 @@ export default function LoginForm() {
<label htmlFor="password" className="block font-lora italic text-[15px] text-text-muted">
Пароль
</label>
<a href="#" className="font-inter text-sm hover:underline transition" style={{ color: '#6B705C' }}>
<button
type="button"
onClick={() => navigate('/forgot-password')}
className="font-inter text-sm hover:underline transition"
style={{ color: 'var(--accent-primary)' }}
>
Забыли пароль?
</a>
</button>
</div>
<div className="relative">
<input
+17 -1
View File
@@ -6,6 +6,7 @@ import { authService } from '../../services/authService';
export default function RegisterForm() {
const [email, setEmail] = useState('');
const [displayName, setDisplayName] = useState('');
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
@@ -35,7 +36,7 @@ export default function RegisterForm() {
setIsLoading(true);
try {
await authService.register({ email, username, password });
await authService.register({ email, display_name: displayName, username, password });
setSuccess('Регистрация успешна! Проверьте почту для подтверждения.');
setTimeout(() => navigate('/auth'), 2000);
} catch (err: any) {
@@ -63,6 +64,21 @@ export default function RegisterForm() {
/>
</div>
<div>
<label htmlFor="displayName" className="block font-lora italic text-[15px] text-text-muted mb-2">
Имя для отображения
</label>
<input
id="displayName"
type="text"
value={displayName}
onChange={(e) => setDisplayName(e.target.value)}
placeholder="Как вас называть"
className="w-full px-0 py-3 bg-transparent border-0 border-b-2 border-gray-200 font-inter text-text-main placeholder:text-text-muted/50 focus:outline-none focus:border-accent-terracotta transition-all duration-300"
required
/>
</div>
<div>
<label htmlFor="username" className="block font-lora italic text-[15px] text-text-muted mb-2">
Никнейм
@@ -0,0 +1,105 @@
import { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Mail, X } from 'lucide-react';
import { authService } from '../../services/authService';
interface VerificationBannerProps {
userEmail: string;
}
export default function VerificationBanner({ userEmail }: VerificationBannerProps) {
const [isVisible, setIsVisible] = useState(true);
const [isLoading, setIsLoading] = useState(false);
const [message, setMessage] = useState('');
const [isSuccess, setIsSuccess] = useState(false);
const handleResendEmail = async () => {
setIsLoading(true);
setMessage('');
try {
await authService.resendVerificationEmail();
setIsSuccess(true);
setMessage('Письмо успешно отправлено! Проверьте свою почту.');
setTimeout(() => {
setMessage('');
}, 5000);
} catch (err: any) {
setIsSuccess(false);
setMessage(err.response?.data?.detail || 'Ошибка отправки письма');
setTimeout(() => {
setMessage('');
}, 5000);
} finally {
setIsLoading(false);
}
};
if (!isVisible) return null;
return (
<AnimatePresence>
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
className="mb-6 relative rounded-2xl p-4 shadow-sm"
style={{
backgroundColor: 'var(--accent-primary-soft)',
border: '2px solid var(--accent-primary)'
}}
>
<button
onClick={() => setIsVisible(false)}
className="absolute top-3 right-3 p-1 hover:opacity-70 transition"
style={{ color: 'var(--accent-primary)' }}
title="Закрыть"
>
<X size={18} />
</button>
<div className="flex items-start gap-3 pr-8">
<div className="flex-shrink-0 p-2 rounded-full" style={{ backgroundColor: 'var(--accent-primary)' }}>
<Mail size={20} className="text-white" />
</div>
<div className="flex-1">
<h3 className="font-inter font-semibold mb-1" style={{ color: 'var(--accent-primary)' }}>
Подтвердите свою почту
</h3>
<p className="text-sm font-inter mb-3" style={{ color: 'var(--text-primary)' }}>
Мы отправили письмо с подтверждением на <span className="font-semibold">{userEmail}</span>.
Проверьте почту и перейдите по ссылке для активации аккаунта.
</p>
{message && (
<motion.div
initial={{ opacity: 0, y: -5 }}
animate={{ opacity: 1, y: 0 }}
className="mb-3 text-sm font-inter"
style={{
color: isSuccess ? 'var(--accent-primary)' : 'var(--error-color)'
}}
>
{message}
</motion.div>
)}
<motion.button
onClick={handleResendEmail}
disabled={isLoading}
whileTap={{ scale: 0.98 }}
className="px-4 py-2 rounded-full font-inter text-sm font-semibold transition hover:shadow-md disabled:opacity-50"
style={{
backgroundColor: 'var(--accent-primary)',
color: 'white'
}}
>
{isLoading ? 'Отправка...' : 'Отправить письмо повторно'}
</motion.button>
</div>
</div>
</motion.div>
</AnimatePresence>
);
}