← back to Watches
src/components/RegisterForm.jsx
198 lines
/**
* Registration Form Component
* Email/password registration with validation
*/
import React, { useState } from 'react';
const RegisterForm = ({ onSuccess, onError }) => {
const [formData, setFormData] = useState({
email: '',
password: '',
confirmPassword: '',
name: ''
});
const [errors, setErrors] = useState({});
const [loading, setLoading] = useState(false);
const validateForm = () => {
const newErrors = {};
// Email validation
const emailRegex = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/;
if (!formData.email) {
newErrors.email = 'Email is required';
} else if (!emailRegex.test(formData.email)) {
newErrors.email = 'Invalid email format';
}
// Password validation
if (!formData.password) {
newErrors.password = 'Password is required';
} else if (formData.password.length < 8) {
newErrors.password = 'Password must be at least 8 characters';
} else if (!/[A-Z]/.test(formData.password)) {
newErrors.password = 'Password must contain an uppercase letter';
} else if (!/[0-9]/.test(formData.password)) {
newErrors.password = 'Password must contain a number';
}
// Confirm password
if (formData.password !== formData.confirmPassword) {
newErrors.confirmPassword = 'Passwords do not match';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
// Clear error when user types
if (errors[name]) {
setErrors(prev => ({ ...prev, [name]: '' }));
}
};
const handleSubmit = async (e) => {
e.preventDefault();
if (!validateForm()) return;
setLoading(true);
try {
const response = await fetch('/api/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: formData.email,
password: formData.password,
name: formData.name || null
})
});
const data = await response.json();
if (data.success) {
onSuccess?.(data);
} else {
setErrors({ form: data.error });
onError?.(data.error);
}
} catch (error) {
setErrors({ form: 'Registration failed. Please try again.' });
onError?.(error.message);
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit} className="space-y-4">
{errors.form && (
<div className="p-3 bg-red-50 border border-red-200 rounded-lg text-red-600 text-sm">
{errors.form}
</div>
)}
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-700 mb-1">
Name (optional)
</label>
<input
type="text"
id="name"
name="name"
value={formData.name}
onChange={handleChange}
className="w-full px-4 py-2 border border-gray-300 rounded-lg
focus:ring-2 focus:ring-blue-500 focus:border-blue-500
transition-colors"
placeholder="Your name"
/>
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
Email
</label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
className={`w-full px-4 py-2 border rounded-lg
focus:ring-2 focus:ring-blue-500 focus:border-blue-500
transition-colors
${errors.email ? 'border-red-500' : 'border-gray-300'}`}
placeholder="you@example.com"
required
/>
{errors.email && (
<p className="mt-1 text-sm text-red-500">{errors.email}</p>
)}
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
Password
</label>
<input
type="password"
id="password"
name="password"
value={formData.password}
onChange={handleChange}
className={`w-full px-4 py-2 border rounded-lg
focus:ring-2 focus:ring-blue-500 focus:border-blue-500
transition-colors
${errors.password ? 'border-red-500' : 'border-gray-300'}`}
placeholder="Min 8 chars, 1 uppercase, 1 number"
required
/>
{errors.password && (
<p className="mt-1 text-sm text-red-500">{errors.password}</p>
)}
</div>
<div>
<label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700 mb-1">
Confirm Password
</label>
<input
type="password"
id="confirmPassword"
name="confirmPassword"
value={formData.confirmPassword}
onChange={handleChange}
className={`w-full px-4 py-2 border rounded-lg
focus:ring-2 focus:ring-blue-500 focus:border-blue-500
transition-colors
${errors.confirmPassword ? 'border-red-500' : 'border-gray-300'}`}
placeholder="Confirm your password"
required
/>
{errors.confirmPassword && (
<p className="mt-1 text-sm text-red-500">{errors.confirmPassword}</p>
)}
</div>
<button
type="submit"
disabled={loading}
className="w-full py-3 px-4 bg-blue-600 text-white font-medium rounded-lg
hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2
disabled:opacity-50 disabled:cursor-not-allowed
transition-colors"
>
{loading ? 'Creating account...' : 'Create Account'}
</button>
</form>
);
};
export default RegisterForm;