'use client'; import { useState, useEffect, FormEvent, type ChangeEvent } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import Link from 'next/link'; import { motion, AnimatePresence } from 'framer-motion'; import { Mail, Lock, Eye, EyeOff, UserPlus, AlertCircle, Flag, User, Check, X, Loader2, Car, Users, Trophy, Shield, Sparkles, } from 'lucide-react'; import Card from '@/components/ui/Card'; import Button from '@/components/ui/Button'; import Input from '@/components/ui/Input'; import Heading from '@/components/ui/Heading'; import { useAuth } from '@/lib/auth/AuthContext'; interface FormErrors { firstName?: string; lastName?: string; email?: string; password?: string; confirmPassword?: string; submit?: string; } interface PasswordStrength { score: number; label: string; color: string; } function checkPasswordStrength(password: string): PasswordStrength { let score = 0; if (password.length >= 8) score++; if (password.length >= 12) score++; if (/[a-z]/.test(password) && /[A-Z]/.test(password)) score++; if (/\d/.test(password)) score++; if (/[^a-zA-Z\d]/.test(password)) score++; if (score <= 1) return { score, label: 'Weak', color: 'bg-red-500' }; if (score <= 2) return { score, label: 'Fair', color: 'bg-warning-amber' }; if (score <= 3) return { score, label: 'Good', color: 'bg-primary-blue' }; return { score, label: 'Strong', color: 'bg-performance-green' }; } const USER_ROLES = [ { icon: Car, title: 'Driver', description: 'Race, track stats, join teams', color: 'primary-blue', }, { icon: Trophy, title: 'League Admin', description: 'Organize leagues and events', color: 'performance-green', }, { icon: Users, title: 'Team Manager', description: 'Manage team and drivers', color: 'purple-400', }, ]; const FEATURES = [ 'Track your racing statistics and progress', 'Join or create competitive leagues', 'Build or join racing teams', 'Connect your iRacing account', 'Compete in organized events', 'Access detailed performance analytics', ]; export default function SignupPage() { const router = useRouter(); const searchParams = useSearchParams(); const { refreshSession, session } = useAuth(); const returnTo = searchParams.get('returnTo') ?? '/onboarding'; const [loading, setLoading] = useState(false); const [checkingAuth, setCheckingAuth] = useState(true); const [showPassword, setShowPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false); const [errors, setErrors] = useState({}); const [formData, setFormData] = useState({ firstName: '', lastName: '', email: '', password: '', confirmPassword: '', }); // Check if already authenticated useEffect(() => { if (session) { // Already logged in, redirect to dashboard or return URL router.replace(returnTo === '/onboarding' ? '/dashboard' : returnTo); return; } // If no session, still check via API for consistency async function checkAuth() { try { const response = await fetch('/api/auth/session'); const data = await response.json(); if (data.authenticated) { // Already logged in, redirect to dashboard or return URL router.replace(returnTo === '/onboarding' ? '/dashboard' : returnTo); } } catch { // Not authenticated, continue showing signup page } finally { setCheckingAuth(false); } } checkAuth(); }, [session, router, returnTo]); const passwordStrength = checkPasswordStrength(formData.password); const passwordRequirements = [ { met: formData.password.length >= 8, label: 'At least 8 characters' }, { met: /[a-z]/.test(formData.password) && /[A-Z]/.test(formData.password), label: 'Upper and lowercase letters' }, { met: /\d/.test(formData.password), label: 'At least one number' }, { met: /[^a-zA-Z\d]/.test(formData.password), label: 'At least one special character' }, ]; const validateForm = (): boolean => { const newErrors: FormErrors = {}; // First name validation const firstName = formData.firstName.trim(); if (!firstName) { newErrors.firstName = 'First name is required'; } else if (firstName.length < 2) { newErrors.firstName = 'First name must be at least 2 characters'; } else if (firstName.length > 25) { newErrors.firstName = 'First name must be no more than 25 characters'; } else if (!/^[A-Za-z\-']+$/.test(firstName)) { newErrors.firstName = 'First name can only contain letters, hyphens, and apostrophes'; } else if (/^(user|test|demo|guest|player)/i.test(firstName)) { newErrors.firstName = 'Please use your real first name, not a nickname'; } // Last name validation const lastName = formData.lastName.trim(); if (!lastName) { newErrors.lastName = 'Last name is required'; } else if (lastName.length < 2) { newErrors.lastName = 'Last name must be at least 2 characters'; } else if (lastName.length > 25) { newErrors.lastName = 'Last name must be no more than 25 characters'; } else if (!/^[A-Za-z\-']+$/.test(lastName)) { newErrors.lastName = 'Last name can only contain letters, hyphens, and apostrophes'; } else if (/^(user|test|demo|guest|player)/i.test(lastName)) { newErrors.lastName = 'Please use your real last name, not a nickname'; } if (!formData.email.trim()) { newErrors.email = 'Email is required'; } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) { newErrors.email = 'Invalid email format'; } // Password strength 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) || !/[A-Z]/.test(formData.password) || !/\d/.test(formData.password)) { newErrors.password = 'Password must contain uppercase, lowercase, and number'; } if (!formData.confirmPassword) { newErrors.confirmPassword = 'Please confirm your password'; } else if (formData.password !== formData.confirmPassword) { newErrors.confirmPassword = 'Passwords do not match'; } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleSubmit = async (e: FormEvent) => { e.preventDefault(); if (loading) return; if (!validateForm()) return; setLoading(true); setErrors({}); try { const { ServiceFactory } = await import('@/lib/services/ServiceFactory'); const serviceFactory = new ServiceFactory(process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001'); const authService = serviceFactory.createAuthService(); // Combine first and last name into display name const displayName = `${formData.firstName} ${formData.lastName}`.trim(); await authService.signup({ email: formData.email, password: formData.password, displayName, }); // Refresh session in context so header updates immediately try { await refreshSession(); } catch (error) { console.error('Failed to refresh session after signup:', error); } // Always redirect to dashboard after signup router.push('/dashboard'); } catch (error) { setErrors({ submit: error instanceof Error ? error.message : 'Signup failed. Please try again.', }); setLoading(false); } }; // Show loading while checking auth if (checkingAuth) { return (
); } return (
{/* Background Pattern */}
{/* Left Side - Info Panel (Hidden on mobile) */}
{/* Logo */}
GridPilot
Start Your Racing Journey

Join thousands of sim racers. One account gives you access to all roles - race as a driver, organize leagues, or manage teams.

{/* Role Cards */}
{USER_ROLES.map((role, index) => (

{role.title}

{role.description}

))}
{/* Features List */}
What you'll get
    {FEATURES.map((feature, index) => (
  • {feature}
  • ))}
{/* Trust Indicators */}
Secure signup
iRacing integration
{/* Right Side - Signup Form */}
{/* Mobile Logo/Header */}
Join GridPilot

Create your account and start racing

{/* Desktop Header */}
Create Account

Get started with your free account

{/* Background accent */}
{/* First Name */}
) => setFormData({ ...formData, firstName: e.target.value })} error={!!errors.firstName} errorMessage={errors.firstName} placeholder="John" disabled={loading} className="pl-10" autoComplete="given-name" />
{/* Last Name */}
) => setFormData({ ...formData, lastName: e.target.value })} error={!!errors.lastName} errorMessage={errors.lastName} placeholder="Smith" disabled={loading} className="pl-10" autoComplete="family-name" />

Your name will be used as-is and cannot be changed later

{/* Name Immutability Warning */}
Important: Your name cannot be changed after signup. Please ensure it's correct.
{/* Email */}
) => setFormData({ ...formData, email: e.target.value })} error={!!errors.email} errorMessage={errors.email} placeholder="you@example.com" disabled={loading} className="pl-10" autoComplete="email" />
{/* Password */}
) => setFormData({ ...formData, password: e.target.value })} error={!!errors.password} errorMessage={errors.password} placeholder="••••••••" disabled={loading} className="pl-10 pr-10" autoComplete="new-password" />
{/* Password Strength */} {formData.password && (
{passwordStrength.label}
{passwordRequirements.map((req, index) => (
{req.met ? ( ) : ( )} {req.label}
))}
)}
{/* Confirm Password */}
) => setFormData({ ...formData, confirmPassword: e.target.value })} error={!!errors.confirmPassword} errorMessage={errors.confirmPassword} placeholder="••••••••" disabled={loading} className="pl-10 pr-10" autoComplete="new-password" />
{formData.confirmPassword && formData.password === formData.confirmPassword && (

Passwords match

)}
{/* Error Message */} {errors.submit && (

{errors.submit}

)}
{/* Submit Button */}
{/* Divider */}
or continue with
{/* Login Link */}

Already have an account?{' '} Sign in

{/* Footer */}

By creating an account, you agree to our{' '} Terms of Service {' '}and{' '} Privacy Policy

{/* Mobile Role Info */}

One account for all roles

{USER_ROLES.map((role) => (
{role.title}
))}
); }