Files
forkmessager/client-web/src/modules/auth/presentation/components/LoginForm.tsx
Халимов Рустам aa69c44a64 Новый стиль
2026-03-31 21:11:05 +03:00

131 lines
5.6 KiB
TypeScript

import React, { useState, FormEvent } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Eye, EyeOff, ArrowRight } from 'lucide-react';
import { useAuthStore } from '../../application/authStore';
import { useLang } from '../../../../core/infrastructure/i18n';
import type { LoginCredentials } from '../../domain/types';
interface Props {
onRegisterClick?: () => void;
enableRegistration?: boolean;
}
export default function LoginForm({ onRegisterClick, enableRegistration }: Props) {
const [credentials, setCredentials] = useState<LoginCredentials>({ username: '', password: '' });
const [showPassword, setShowPassword] = useState(false);
const [error, setError] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const { login } = useAuthStore();
const { lang } = useLang();
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
setError('');
setIsSubmitting(true);
try {
await login(credentials.username, credentials.password);
} catch (err: unknown) {
setError(err instanceof Error ? err.message : 'Ошибка');
setIsSubmitting(false);
}
};
return (
<>
<AnimatePresence>
{error && (
<motion.div
initial={{ opacity: 0, scale: 0.98 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.98 }}
className="mb-6 p-4 rounded-2xl bg-error/10 border-none text-error text-[13px] font-bold text-center uppercase tracking-widest"
role="alert"
>
{error}
</motion.div>
)}
</AnimatePresence>
<form onSubmit={handleSubmit} className="space-y-4" autoComplete="off">
<div className="knot-input-group">
<label className="block text-[11px] font-black uppercase tracking-[0.2em] text-on-surface-variant/40 mb-3 ml-1">
{lang === 'ru' ? 'Имя пользователя' : 'Username'}
</label>
<div className="relative">
<span className="material-symbols-outlined absolute left-4 top-1/2 -translate-y-1/2 text-on-surface-variant/40 text-[20px]">person</span>
<input
type="text"
value={credentials.username}
onChange={(e) => setCredentials({ ...credentials, username: e.target.value.replace(/[^a-zA-Z0-9_]/g, '') })}
placeholder="username"
className="w-full pl-12 pr-4 py-4 rounded-2xl bg-surface-container-highest/60 text-on-surface placeholder-on-surface-variant/20 border-none focus:ring-2 focus:ring-primary/20 transition-all outline-none text-[15px]"
required
autoFocus
autoComplete="off"
/>
</div>
</div>
<div className="knot-input-group">
<label className="block text-[11px] font-black uppercase tracking-[0.2em] text-on-surface-variant/40 mb-3 ml-1">
{lang === 'ru' ? 'Пароль' : 'Password'}
</label>
<div className="relative">
<span className="material-symbols-outlined absolute left-4 top-1/2 -translate-y-1/2 text-on-surface-variant/40 text-[20px]">lock</span>
<input
type={showPassword ? 'text' : 'password'}
value={credentials.password}
onChange={(e) => setCredentials({ ...credentials, password: e.target.value })}
placeholder={lang === 'ru' ? '••••••••' : '••••••••'}
className="w-full pl-12 pr-12 py-4 rounded-2xl bg-surface-container-highest/60 text-on-surface placeholder-on-surface-variant/20 border-none focus:ring-2 focus:ring-primary/20 transition-all outline-none text-[15px]"
required
autoComplete="current-password"
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-4 top-1/2 -translate-y-1/2 text-on-surface-variant/40 hover:text-primary transition-colors slide-on-ice"
>
<span className="material-symbols-outlined text-[20px]">{showPassword ? 'visibility_off' : 'visibility'}</span>
</button>
</div>
</div>
<button
disabled={isSubmitting}
type="submit"
className="knot-button-primary w-full py-4 mt-8 flex items-center justify-center gap-3 disabled:opacity-50 slide-on-ice"
>
{isSubmitting ? (
<div className="w-5 h-5 border-2 border-on-primary/30 border-t-on-primary rounded-full animate-spin" />
) : (
<>
<span className="font-black uppercase tracking-widest text-[14px]">
{lang === 'ru' ? 'Войти' : 'Continue'}
</span>
<span className="material-symbols-outlined text-[20px]">arrow_forward</span>
</>
)}
</button>
</form>
{enableRegistration && onRegisterClick && (
<div className="mt-10 pt-8 border-t border-on-surface-variant/5 flex flex-col items-center gap-4">
<p className="text-on-surface-variant/40 text-[11px] font-black uppercase tracking-widest">
{lang === 'ru' ? 'Нет аккаунта?' : "New to Knot?"}
</p>
<button
onClick={onRegisterClick}
className="text-primary hover:text-primary/80 text-[13px] font-black uppercase tracking-widest transition-all slide-on-ice"
type="button"
>
{lang === 'ru' ? 'Создать аккаунт' : 'Create Account'}
</button>
</div>
)}
</>
);
}