/* eslint-disable @next/next/no-img-element -- External or generated image URLs; next/image migration is out of scope. */ import { useState, useEffect } from 'react'; import { useRouter } from 'next/router'; import Link from 'next/link'; import AuthLayout from '../components/AuthLayout'; import AnimatedFireLogo from '../components/AnimatedFireLogo'; import { Button, Input } from '../components/ui'; import { VOCAB } from '../lib/collection-vocabulary.js'; export default function Signup() { const router = useRouter(); const [formData, setFormData] = useState({ email: '', password: '', confirmPassword: '', firstName: '', lastName: '', username: '' }); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [validationErrors, setValidationErrors] = useState({}); const [profileImage, setProfileImage] = useState(''); const [uploadedImage, setUploadedImage] = useState(null); const [imageLoading, setImageLoading] = useState(false); // Generate initial random avatar const generateRandomAvatar = async () => { setImageLoading(true); try { const seed = Math.random().toString(36).substring(7); const avatarUrl = `https://api.dicebear.com/9.x/adventurer-neutral/svg?seed=${seed}&size=200&backgroundColor=f3f4f6`; setProfileImage(avatarUrl); setUploadedImage(null); // Clear any uploaded image } catch (error) { console.error('Error generating avatar:', error); } finally { setImageLoading(false); } }; useEffect(() => { // eslint-disable-next-line react-hooks/set-state-in-effect -- seed signup avatar preview on mount generateRandomAvatar(); }, []); const handleInputChange = (field, value) => { setFormData(prev => ({ ...prev, [field]: value })); // Clear errors when user starts typing if (error) setError(''); if (validationErrors[field]) { setValidationErrors(prev => ({ ...prev, [field]: '' })); } }; const handleImageUpload = async (event) => { const file = event.target.files[0]; if (!file) return; // Validate file type if (!file.type.startsWith('image/')) { setValidationErrors(prev => ({ ...prev, image: 'Please select a valid image file' })); return; } // Validate file size (max 5MB) if (file.size > 5 * 1024 * 1024) { setValidationErrors(prev => ({ ...prev, image: 'Image must be less than 5MB' })); return; } setImageLoading(true); try { // Create FormData for file upload const formData = new FormData(); formData.append('file', file); const response = await fetch('/api/user/avatar', { method: 'POST', body: formData }); if (response.ok) { const data = await response.json(); setUploadedImage(data.url); setProfileImage(data.url); setValidationErrors(prev => { const newErrors = { ...prev }; delete newErrors.image; return newErrors; }); } else { throw new Error('Failed to upload image'); } } catch (error) { console.error('Error uploading image:', error); setValidationErrors(prev => ({ ...prev, image: 'Failed to upload image. Please try again.' })); } finally { setImageLoading(false); } }; const validateForm = () => { const errors = {}; // Email validation if (!formData.email) { errors.email = 'Email is required'; } else if (!/\S+@\S+\.\S+/.test(formData.email)) { errors.email = 'Please enter a valid email address'; } // Username validation if (!formData.username) { errors.username = 'Username is required'; } else if (formData.username.length < 3) { errors.username = 'Username must be at least 3 characters'; } else if (!/^[a-zA-Z0-9_]+$/.test(formData.username)) { errors.username = 'Username can only contain letters, numbers, and underscores'; } // Password validation if (!formData.password) { errors.password = 'Password is required'; } else if (formData.password.length < 6) { errors.password = 'Password must be at least 6 characters'; } // Confirm password validation if (!formData.confirmPassword) { errors.confirmPassword = 'Please confirm your password'; } else if (formData.password !== formData.confirmPassword) { errors.confirmPassword = 'Passwords do not match'; } // Name validation if (!formData.firstName.trim()) { errors.firstName = 'First name is required'; } if (!formData.lastName.trim()) { errors.lastName = 'Last name is required'; } setValidationErrors(errors); return Object.keys(errors).length === 0; }; const handleSubmit = async (e) => { e.preventDefault(); if (!validateForm()) { return; } setLoading(true); setError(''); try { const response = await fetch('/api/auth/register', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ email: formData.email, password: formData.password, firstName: formData.firstName, lastName: formData.lastName, username: formData.username, profileImage: profileImage // Send the current profile image (either uploaded or generated) }) }); const data = await response.json(); if (response.ok) { // Store the JWT token in localStorage localStorage.setItem('auth_token', data.token); // Redirect to dashboard router.push('/dashboard'); } else { setError(data.error || 'Registration failed'); } } catch (error) { console.error('Registration error:', error); setError('Network error. Please try again.'); } finally { setLoading(false); } }; return (

Join Deck Hearth

Create your account to start building {VOCAB.MY_COLLECTION}

{error && (

{error}

)} {/* Profile Image Section */}
{imageLoading ? (
) : (
{profileImage && ( Profile )}
)}
{validationErrors.image && (

{validationErrors.image}

)}
handleInputChange('firstName', e.target.value)} placeholder="John" error={validationErrors.firstName} /> handleInputChange('lastName', e.target.value)} placeholder="Doe" error={validationErrors.lastName} />
handleInputChange('username', e.target.value)} placeholder="johndoe123" error={validationErrors.username} /> handleInputChange('email', e.target.value)} placeholder="john@example.com" error={validationErrors.email} /> handleInputChange('password', e.target.value)} placeholder="At least 6 characters" error={validationErrors.password} /> handleInputChange('confirmPassword', e.target.value)} placeholder="Confirm your password" error={validationErrors.confirmPassword} />

Already have an account?{' '} Sign in here

); }