161 lines
6.9 KiB
TypeScript
161 lines
6.9 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 { RegisterCredentials } from '../../domain/types';
|
||
|
||
interface Props {
|
||
onLoginClick: () => void;
|
||
enableRegistration?: boolean;
|
||
}
|
||
|
||
export default function RegisterForm({ onLoginClick, enableRegistration }: Props) {
|
||
const [credentials, setCredentials] = useState<RegisterCredentials>({ username: '', displayName: '', password: '', bio: '' });
|
||
const [showPassword, setShowPassword] = useState(false);
|
||
const [error, setError] = useState('');
|
||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||
|
||
const { register } = useAuthStore();
|
||
const { lang } = useLang();
|
||
|
||
if (!enableRegistration) return null;
|
||
|
||
const handleSubmit = async (e: FormEvent) => {
|
||
e.preventDefault();
|
||
setError('');
|
||
setIsSubmitting(true);
|
||
|
||
try {
|
||
await register(credentials.username, credentials.displayName || credentials.username, credentials.password, credentials.bio);
|
||
} catch (err: unknown) {
|
||
setError(err instanceof Error ? err.message : 'Ошибка');
|
||
setIsSubmitting(false);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<>
|
||
<AnimatePresence>
|
||
{error && (
|
||
<motion.div
|
||
initial={{ opacity: 0, y: -10 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
exit={{ opacity: 0, y: -10 }}
|
||
className="mb-6 p-3.5 rounded-xl bg-red-500/10 border border-red-500/20 text-red-400 text-sm font-medium text-center"
|
||
role="alert"
|
||
>
|
||
{error}
|
||
</motion.div>
|
||
)}
|
||
</AnimatePresence>
|
||
|
||
<form onSubmit={handleSubmit} className="space-y-4" autoComplete="off">
|
||
<div>
|
||
<label className="block text-sm font-semibold text-zinc-300 mb-2">
|
||
Username <span className="text-zinc-600 font-normal ml-1">({lang === 'ru' ? 'латиница, нельзя изменить' : 'latin, cannot change'})</span>
|
||
</label>
|
||
<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 px-4 py-3.5 rounded-xl bg-[#18181b] border border-white/5 text-white placeholder-zinc-600 focus:border-[#9b66ff]/50 focus:bg-[#1f1f22] transition-colors outline-none text-[15px]"
|
||
required
|
||
autoFocus
|
||
autoComplete="off"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-semibold text-zinc-300 mb-2">
|
||
{lang === 'ru' ? 'Отображаемое имя' : 'Display Name'}
|
||
</label>
|
||
<input
|
||
type="text"
|
||
value={credentials.displayName}
|
||
onChange={(e) => setCredentials({ ...credentials, displayName: e.target.value })}
|
||
placeholder={lang === 'ru' ? 'Ваше имя (любой язык)' : 'Your name'}
|
||
className="w-full px-4 py-3.5 rounded-xl bg-[#18181b] border border-white/5 text-white placeholder-zinc-600 focus:border-[#9b66ff]/50 focus:bg-[#1f1f22] transition-colors outline-none text-[15px]"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-semibold text-zinc-300 mb-2">
|
||
{lang === 'ru' ? 'Пароль' : 'Password'}
|
||
</label>
|
||
<div className="relative">
|
||
<input
|
||
type={showPassword ? 'text' : 'password'}
|
||
value={credentials.password}
|
||
onChange={(e) => setCredentials({ ...credentials, password: e.target.value })}
|
||
placeholder={lang === 'ru' ? 'Введите пароль' : 'Enter password'}
|
||
className="w-full px-4 py-3.5 rounded-xl bg-[#18181b] border border-white/5 text-white placeholder-zinc-600 focus:border-[#9b66ff]/50 focus:bg-[#1f1f22] transition-colors pr-12 outline-none text-[15px]"
|
||
required
|
||
autoComplete="new-password"
|
||
minLength={8}
|
||
/>
|
||
<button
|
||
type="button"
|
||
onClick={() => setShowPassword(!showPassword)}
|
||
className="absolute right-4 top-1/2 -translate-y-1/2 text-zinc-500 hover:text-zinc-300 transition-colors"
|
||
>
|
||
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
|
||
</button>
|
||
</div>
|
||
<p className="mt-2 text-[12px] text-zinc-500 flex items-center gap-1.5 font-medium">
|
||
<span className="w-1 h-1 rounded-full bg-[#9b66ff]" />
|
||
{lang === 'ru' ? 'Минимум 8 символов, буквы и цифры' : 'Minimum 8 characters, letters and numbers'}
|
||
</p>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-semibold text-zinc-300 mb-2">
|
||
{lang === 'ru' ? 'О себе' : 'About me'}
|
||
</label>
|
||
<input
|
||
type="text"
|
||
value={credentials.bio}
|
||
onChange={(e) => setCredentials({ ...credentials, bio: e.target.value })}
|
||
placeholder={lang === 'ru' ? 'Расскажите о себе (необязательно)' : 'Tell about yourself (optional)'}
|
||
className="w-full px-4 py-3.5 rounded-xl bg-[#18181b] border border-white/5 text-white placeholder-zinc-600 focus:border-[#9b66ff]/50 focus:bg-[#1f1f22] transition-colors outline-none text-[15px]"
|
||
/>
|
||
</div>
|
||
|
||
<motion.button
|
||
whileHover={{ scale: 1.01 }}
|
||
whileTap={{ scale: 0.99 }}
|
||
disabled={isSubmitting}
|
||
type="submit"
|
||
className="w-full py-3.5 px-4 rounded-xl bg-[#8b5cf6] hover:bg-[#7c3aed] text-white font-semibold flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed mt-8 transition-colors text-[16px]"
|
||
style={{ marginTop: '32px' }}
|
||
>
|
||
{isSubmitting ? (
|
||
<div className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
|
||
) : (
|
||
<>
|
||
{lang === 'ru' ? 'Создать аккаунт' : 'Create account'}
|
||
<ArrowRight size={18} />
|
||
</>
|
||
)}
|
||
</motion.button>
|
||
</form>
|
||
|
||
{enableRegistration && (
|
||
<div className="mt-8 pt-6 border-t border-white/5 flex justify-center items-center gap-2">
|
||
<p className="text-zinc-500 text-[13px] font-medium">
|
||
{lang === 'ru' ? 'Уже есть аккаунт?' : 'Already have an account?'}
|
||
</p>
|
||
<button
|
||
onClick={onLoginClick}
|
||
className="text-[#7c3aed] hover:text-[#8b5cf6] text-[13px] font-semibold transition-colors"
|
||
type="button"
|
||
>
|
||
{lang === 'ru' ? 'Войти' : 'Login'}
|
||
</button>
|
||
</div>
|
||
)}
|
||
</>
|
||
);
|
||
}
|