diff --git a/pages/api/auth/register.js b/pages/api/auth/register.js index 708416d..ea25ec4 100644 --- a/pages/api/auth/register.js +++ b/pages/api/auth/register.js @@ -22,33 +22,69 @@ export default async function handler(req, res) { } try { - const { email, password } = req.body; + const { email, password, firstName, lastName, username, profileImage } = req.body; - if (!email || !password) { - return res.status(400).json({ error: 'Email and password are required' }); + // Validate required fields + if (!email || !password || !firstName || !lastName || !username) { + return res.status(400).json({ error: 'All fields are required' }); } if (password.length < 6) { return res.status(400).json({ error: 'Password must be at least 6 characters' }); } - // Check if user already exists + // Validate username + if (username.length < 3) { + return res.status(400).json({ error: 'Username must be at least 3 characters' }); + } + + if (!/^[a-zA-Z0-9_]+$/.test(username)) { + return res.status(400).json({ error: 'Username can only contain letters, numbers, and underscores' }); + } + + // Check if user already exists (email or username) const existingUser = await sql` - SELECT id FROM users WHERE email = ${email} + SELECT id FROM users WHERE email = ${email} OR username = ${username} `; if (existingUser.rows.length > 0) { - return res.status(409).json({ error: 'User already exists' }); + // Check which field conflicts + const conflictUser = await sql` + SELECT email, username FROM users WHERE email = ${email} OR username = ${username} + `; + + const conflict = conflictUser.rows[0]; + if (conflict.email === email) { + return res.status(409).json({ error: 'Email already exists' }); + } else { + return res.status(409).json({ error: 'Username already taken' }); + } } // Hash password const hashedPassword = await bcrypt.hash(password, 12); - // Create user + // Create user with all fields const result = await sql` - INSERT INTO users (email, password, role) - VALUES (${email}, ${hashedPassword}, ${'user'}) - RETURNING id, email, role, created_at + INSERT INTO users ( + email, + password, + first_name, + last_name, + username, + profile_image_url, + role + ) + VALUES ( + ${email}, + ${hashedPassword}, + ${firstName}, + ${lastName}, + ${username}, + ${profileImage || null}, + ${'user'} + ) + RETURNING id, email, first_name, last_name, username, profile_image_url, role, created_at `; const user = result.rows[0]; @@ -110,9 +146,21 @@ export default async function handler(req, res) { { expiresIn: '24h' } ); + // Return user data without password + const userResponse = { + id: user.id, + email: user.email, + firstName: user.first_name, + lastName: user.last_name, + username: user.username, + profileImage: user.profile_image_url, + role: user.role, + createdAt: user.created_at + }; + res.status(201).json({ success: true, - user, + user: userResponse, token }); diff --git a/pages/signup.js b/pages/signup.js index c94530a..bb0ec4b 100644 --- a/pages/signup.js +++ b/pages/signup.js @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { useRouter } from 'next/router'; import Link from 'next/link'; import AuthLayout from '../components/AuthLayout'; @@ -11,11 +11,34 @@ export default function Signup() { password: '', confirmPassword: '', firstName: '', - lastName: '' + 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 + useEffect(() => { + generateRandomAvatar(); + }, []); + + 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); + } + }; const handleInputChange = (field, value) => { setFormData(prev => ({ @@ -32,6 +55,62 @@ export default function Signup() { } }; + 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 = {}; @@ -42,6 +121,15 @@ export default function Signup() { 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'; @@ -89,7 +177,9 @@ export default function Signup() { email: formData.email, password: formData.password, firstName: formData.firstName, - lastName: formData.lastName + lastName: formData.lastName, + username: formData.username, + profileImage: profileImage // Send the current profile image (either uploaded or generated) }) }); @@ -144,6 +234,80 @@ export default function Signup() { )} + {/* Profile Image Section */} +
{validationErrors.image}
+ )} +{validationErrors.username}
+ )} +