Files
forkmessager/client-web/src/modules/auth/presentation/components/RegisterForm.tsx
T

161 lines
6.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>
)}
</>
);
}