FinMK / frontend /src /pages /Register.jsx
Kumar
Refactor: Exclude PDF and CSV files from Git to fix HF push error
24e6f5b
import { useState, useEffect } from 'react';
import { useAuth } from '../context/AuthContext';
import { Link } from 'react-router-dom';
import { User, Mail, Lock, ArrowRight, Loader, CheckCircle, Eye, EyeOff, XCircle, Check, Wallet } from 'lucide-react';
const Register = () => {
const [formData, setFormData] = useState({ username: '', email: '', password: '', confirmPassword: '' });
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const [isPasswordFocused, setIsPasswordFocused] = useState(false);
const { register } = useAuth();
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState('');
const [passwordCriteria, setPasswordCriteria] = useState({ length: false, upper: false, lower: false, number: false, special: false });
const [passwordsMatch, setPasswordsMatch] = useState(true);
const [allValid, setAllValid] = useState(false);
useEffect(() => {
const { password, confirmPassword } = formData;
const criteria = {
length: password.length >= 8,
upper: /[A-Z]/.test(password),
lower: /[a-z]/.test(password),
number: /[0-9]/.test(password),
special: /[^A-Za-z0-9]/.test(password),
};
setPasswordCriteria(criteria);
setPasswordsMatch(password === confirmPassword || confirmPassword === '');
setAllValid(Object.values(criteria).every(Boolean) && password === confirmPassword && password !== '');
}, [formData.password, formData.confirmPassword]);
const handleSubmit = async (e) => {
e.preventDefault();
if (isSubmitting) return;
if (!allValid) { setError('Please ensure all requirements are met and passwords match.'); return; }
setIsSubmitting(true);
setError('');
try {
await register(formData.username, formData.email, formData.password);
} catch (err) {
setError(err.response?.data?.username?.[0] || 'Registration failed. Please try again.');
setIsSubmitting(false);
}
};
const RequirementItem = ({ met, text }) => (
<div style={{ display: 'flex', alignItems: 'center', gap: '0.4rem', fontSize: '0.78rem', color: met ? '#34d399' : 'var(--text-muted)', transition: 'color 0.2s', marginBottom: '0.2rem' }}>
{met ? <Check size={12} color="#34d399" /> : <div style={{ width: 12, height: 12, borderRadius: '50%', border: '1px solid currentColor', flexShrink: 0 }} />}
{text}
</div>
);
return (
<div className="home-container" style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '100vh', padding: '2rem 1rem' }}>
<div className="bg-blobs">
<div className="blob blob-2 animate-blob" />
<div className="blob blob-3 animate-blob animation-delay-2000" />
<div className="blob blob-1 animate-blob animation-delay-4000" />
</div>
<div className="grid-overlay" />
<div className="glass-pro animate-slide-up" style={{
padding: '2.25rem',
width: '100%',
maxWidth: '400px',
borderRadius: '20px',
position: 'relative',
overflow: 'hidden',
border: '1px solid rgba(139,92,246,0.2)',
boxShadow: '0 30px 80px rgba(0,0,0,0.6), 0 0 60px rgba(139,92,246,0.08)',
margin: '1rem',
}}>
{/* Decorative orb */}
<div style={{
position: 'absolute', top: '-60px', left: '-60px',
width: '200px', height: '200px',
background: 'radial-gradient(circle, rgba(139,92,246,0.12) 0%, transparent 70%)',
pointerEvents: 'none',
}} />
{/* Header */}
<div style={{ textAlign: 'center', marginBottom: '1.75rem' }}>
<div className="brand-icon" style={{ width: '48px', height: '48px', margin: '0 auto 1.25rem', borderRadius: '14px', background: 'linear-gradient(135deg, #8b5cf6, #ec4899)', justifyContent: 'center', alignItems: 'center', display: 'flex', boxShadow: '0 4px 14px rgba(139,92,246,0.4)' }}>
<Wallet size={24} />
</div>
<h2 style={{ fontSize: '1.75rem', marginBottom: '0.4rem', letterSpacing: '-0.02em', background: 'linear-gradient(135deg, #c084fc, #f472b6)', WebkitBackgroundClip: 'text', backgroundClip: 'text', color: 'transparent' }}>
Create Account
</h2>
<p style={{ color: 'var(--text-muted)', fontSize: '0.88rem' }}>Join FinMK and take control of your wealth</p>
</div>
{/* Error */}
{error && (
<div className="animate-slide-up" style={{
backgroundColor: 'rgba(244,63,94,0.08)',
border: '1px solid rgba(244,63,94,0.2)',
color: '#fb7185',
padding: '0.65rem 0.875rem',
borderRadius: '10px',
marginBottom: '1.25rem',
display: 'flex',
alignItems: 'center',
gap: '0.5rem',
fontSize: '0.875rem'
}}>
<span></span> {error}
</div>
)}
<form onSubmit={handleSubmit}>
<div className="auth-input-group delay-100 animate-slide-up">
<input type="text" className="input-field-pro" placeholder="Username"
value={formData.username} onChange={(e) => setFormData({ ...formData, username: e.target.value })} required />
<User className="auth-input-icon" size={18} />
</div>
<div className="auth-input-group delay-200 animate-slide-up">
<input type="email" className="input-field-pro" placeholder="Email Address"
value={formData.email} onChange={(e) => setFormData({ ...formData, email: e.target.value })} required />
<Mail className="auth-input-icon" size={18} />
</div>
<div className="auth-input-group delay-300 animate-slide-up" style={{ marginBottom: '0.75rem' }}>
<input type={showPassword ? 'text' : 'password'} className="input-field-pro" placeholder="Password"
value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
onFocus={() => setIsPasswordFocused(true)}
onBlur={() => setIsPasswordFocused(false)}
required style={{ paddingRight: '2.8rem' }} />
<Lock className="auth-input-icon" size={18} />
<button type="button" onClick={() => setShowPassword(!showPassword)} style={{ position: 'absolute', right: '0.875rem', top: '50%', transform: 'translateY(-50%)', background: 'none', border: 'none', color: 'var(--text-muted)', cursor: 'pointer', padding: 0, display: 'flex' }}>
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
</button>
</div>
<div className="auth-input-group delay-300 animate-slide-up" style={{ marginBottom: '0.75rem' }}>
<input type={showConfirmPassword ? 'text' : 'password'} className="input-field-pro" placeholder="Confirm Password"
value={formData.confirmPassword}
onChange={(e) => setFormData({ ...formData, confirmPassword: e.target.value })}
required style={{ paddingRight: '2.8rem', borderColor: (!passwordsMatch && formData.confirmPassword) ? 'rgba(244,63,94,0.5)' : 'var(--glass-border)' }} />
<Lock className="auth-input-icon" size={18} />
<button type="button" onClick={() => setShowConfirmPassword(!showConfirmPassword)} style={{ position: 'absolute', right: '0.875rem', top: '50%', transform: 'translateY(-50%)', background: 'none', border: 'none', color: 'var(--text-muted)', cursor: 'pointer', padding: 0, display: 'flex' }}>
{showConfirmPassword ? <EyeOff size={18} /> : <Eye size={18} />}
</button>
</div>
{/* Password strength */}
{(isPasswordFocused || formData.password.length > 0) && (
<div className="animate-slide-up" style={{ background: 'rgba(0,0,0,0.2)', padding: '0.75rem', borderRadius: '10px', marginBottom: '1rem', border: '1px solid var(--glass-border)' }}>
<p style={{ fontSize: '0.7rem', color: 'var(--text-muted)', marginBottom: '0.5rem', textTransform: 'uppercase', letterSpacing: '0.08em', fontWeight: 600 }}>Requirements</p>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '0.15rem' }}>
<RequirementItem met={passwordCriteria.length} text="8+ Characters" />
<RequirementItem met={passwordCriteria.upper} text="Uppercase" />
<RequirementItem met={passwordCriteria.lower} text="Lowercase" />
<RequirementItem met={passwordCriteria.number} text="Number" />
<RequirementItem met={passwordCriteria.special} text="Symbol" />
<RequirementItem met={passwordsMatch && formData.confirmPassword !== ''} text="Passwords match" />
</div>
</div>
)}
<button type="submit" className="btn-primary delay-300 animate-slide-up" disabled={isSubmitting || !allValid} style={{
width: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center',
gap: '0.5rem', marginTop: '0.25rem', padding: '0.8rem', borderRadius: '10px',
background: 'linear-gradient(135deg, #8b5cf6, #ec4899)',
boxShadow: allValid ? '0 8px 24px rgba(139,92,246,0.4)' : 'none',
opacity: (!allValid && !isSubmitting) ? 0.55 : 1,
cursor: (!allValid && !isSubmitting) ? 'not-allowed' : 'pointer',
fontSize: '0.95rem',
}}>
{isSubmitting ? (
<><Loader className="spinning" size={18} /> Creating Account...</>
) : (
<><CheckCircle size={18} /> Create Account</>
)}
</button>
</form>
<div className="delay-300 animate-slide-up" style={{ marginTop: '1.5rem', textAlign: 'center', borderTop: '1px solid var(--glass-border)', paddingTop: '1.25rem' }}>
<p style={{ color: 'var(--text-muted)', fontSize: '0.875rem' }}>
Already have an account?{' '}
<Link to="/login" style={{ color: '#c084fc', marginLeft: '0.25rem', fontWeight: 600, textDecoration: 'none' }}>
Sign in →
</Link>
</p>
</div>
</div>
</div>
);
};
export default Register;