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

167 lines
7.8 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, scale: 0.98 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.98 }}
className="mb-8 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 (latin only)'}
</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]">alternate_email</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' ? 'Отображаемое имя' : 'Display Name'}
</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]">face</span>
<input
type="text"
value={credentials.displayName}
onChange={(e) => setCredentials({ ...credentials, displayName: e.target.value })}
placeholder={lang === 'ru' ? 'Ваше имя' : 'Your name'}
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]"
/>
</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_open</span>
<input
type={showPassword ? 'text' : 'password'}
value={credentials.password}
onChange={(e) => setCredentials({ ...credentials, password: e.target.value })}
placeholder="••••••••"
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="new-password"
minLength={8}
/>
<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>
<p className="mt-3 text-[10px] text-on-surface-variant/40 flex items-center gap-1.5 font-black uppercase tracking-widest ml-1">
<span className="w-2.5 h-2.5 rounded-full border-2 border-primary" />
{lang === 'ru' ? '8+ символов, любые знаки' : '8+ characters required'}
</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>
<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' ? 'Создать аккаунт' : 'Create Account'}
</span>
<span className="material-symbols-outlined text-[20px]">check_circle</span>
</>
)}
</button>
</form>
{enableRegistration && (
<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' ? 'Уже есть аккаунт?' : 'Already have an account?'}
</p>
<button
onClick={onLoginClick}
className="text-primary hover:text-primary/80 text-[13px] font-black uppercase tracking-widest transition-all slide-on-ice"
type="button"
>
{lang === 'ru' ? 'Войти в систему' : 'Sign In instead'}
</button>
</div>
)}
</>
);
}