fix issues

This commit is contained in:
2026-01-01 20:31:05 +01:00
parent 9005a8327c
commit 206a03ec48
267 changed files with 3632 additions and 452 deletions

View File

@@ -1,7 +1,8 @@
'use client';
import { useState, FormEvent, type ChangeEvent } from 'react';
import { useState, useEffect, FormEvent, type ChangeEvent } from 'react';
import { useRouter } from 'next/navigation';
import { useAuth } from '@/lib/auth/AuthContext';
import Link from 'next/link';
import { motion } from 'framer-motion';
import {
@@ -30,6 +31,7 @@ interface SuccessState {
export default function ForgotPasswordPage() {
const router = useRouter();
const { session } = useAuth();
const [loading, setLoading] = useState(false);
const [errors, setErrors] = useState<FormErrors>({});
@@ -38,6 +40,13 @@ export default function ForgotPasswordPage() {
email: '',
});
// Check if user is already authenticated
useEffect(() => {
if (session) {
router.replace('/dashboard');
}
}, [session, router]);
const validateForm = (): boolean => {
const newErrors: FormErrors = {};

View File

@@ -2,7 +2,7 @@
import { useState, useEffect } from 'react';
import Link from 'next/link';
import { useSearchParams } from 'next/navigation';
import { useRouter, useSearchParams } from 'next/navigation';
import { motion, AnimatePresence, useReducedMotion } from 'framer-motion';
import {
Gamepad2,
@@ -19,6 +19,7 @@ import {
import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import Heading from '@/components/ui/Heading';
import { useAuth } from '@/lib/auth/AuthContext';
interface ConnectionStep {
id: number;
@@ -63,7 +64,9 @@ const BENEFITS = [
];
export default function IracingAuthPage() {
const router = useRouter();
const searchParams = useSearchParams();
const { session } = useAuth();
const returnTo = searchParams.get('returnTo') ?? '/dashboard';
const startUrl = `/auth/iracing/start?returnTo=${encodeURIComponent(returnTo)}`;
@@ -72,6 +75,13 @@ export default function IracingAuthPage() {
const [activeStep, setActiveStep] = useState(0);
const [isHovering, setIsHovering] = useState(false);
// Check if user is already authenticated
useEffect(() => {
if (session) {
router.replace('/dashboard');
}
}, [session, router]);
useEffect(() => {
setIsMounted(true);
}, []);

View File

@@ -1,9 +1,9 @@
'use client';
import { useState, FormEvent, type ChangeEvent } from 'react';
import { useState, useEffect } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import Link from 'next/link';
import { motion } from 'framer-motion';
import { motion, AnimatePresence } from 'framer-motion';
import {
Mail,
Lock,
@@ -24,91 +24,101 @@ import Heading from '@/components/ui/Heading';
import { useAuth } from '@/lib/auth/AuthContext';
import AuthWorkflowMockup from '@/components/auth/AuthWorkflowMockup';
import UserRolesPreview from '@/components/auth/UserRolesPreview';
interface FormErrors {
email?: string;
password?: string;
submit?: string;
}
import { EnhancedFormError, FormErrorSummary } from '@/components/errors/EnhancedFormError';
import { useEnhancedForm } from '@/lib/hooks/useEnhancedForm';
import { validateLoginForm, type LoginFormValues } from '@/lib/utils/validation';
import { logErrorWithContext } from '@/lib/utils/errorUtils';
export default function LoginPage() {
const router = useRouter();
const searchParams = useSearchParams();
const { refreshSession } = useAuth();
const { refreshSession, session } = useAuth();
const returnTo = searchParams.get('returnTo') ?? '/dashboard';
const [loading, setLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const [errors, setErrors] = useState<FormErrors>({});
const [formData, setFormData] = useState({
email: '',
password: '',
});
const [showErrorDetails, setShowErrorDetails] = useState(false);
const validateForm = (): boolean => {
const newErrors: FormErrors = {};
if (!formData.email.trim()) {
newErrors.email = 'Email is required';
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
newErrors.email = 'Invalid email format';
// Check if user is already authenticated
useEffect(() => {
if (session) {
router.replace(returnTo);
}
}, [session, router, returnTo]);
if (!formData.password) {
newErrors.password = 'Password is required';
}
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 {
// Use enhanced form hook
const {
formState,
setFormState,
handleChange,
handleSubmit,
setFormError,
} = useEnhancedForm<LoginFormValues>({
initialValues: {
email: '',
password: '',
rememberMe: false,
},
validate: validateLoginForm,
component: 'LoginPage',
onSubmit: async (values) => {
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();
// Log the attempt for debugging
logErrorWithContext(
{ message: 'Login attempt', values: { ...values, password: '[REDACTED]' } },
{
component: 'LoginPage',
action: 'login-submit',
formData: { ...values, password: '[REDACTED]' },
}
);
await authService.login({
email: formData.email,
password: formData.password,
email: values.email,
password: values.password,
rememberMe: values.rememberMe,
});
// Refresh session in context so header updates immediately
await refreshSession();
router.push(returnTo);
} catch (error) {
setErrors({
submit: error instanceof Error ? error.message : 'Login failed. Please try again.',
});
setLoading(false);
}
};
},
onError: (error, values) => {
// Show error details toggle in development
if (process.env.NODE_ENV === 'development') {
setShowErrorDetails(true);
}
},
onSuccess: () => {
// Reset error details on success
setShowErrorDetails(false);
},
});
const handleDemoLogin = async () => {
setLoading(true);
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();
await authService.demoLogin({ role: 'driver' });
// Get rememberMe value safely
const rememberMe = formState.fields.rememberMe?.value ?? false;
await authService.demoLogin({
role: 'driver',
rememberMe,
});
await new Promise(resolve => setTimeout(resolve, 500));
router.push(returnTo);
} catch (error) {
setErrors({
submit: 'Demo login failed. Please try again.',
setFormError('Demo login failed. Please try again.');
logErrorWithContext(error, {
component: 'LoginPage',
action: 'demo-login',
});
setLoading(false);
}
};
@@ -197,13 +207,14 @@ export default function LoginPage() {
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-500" />
<Input
id="email"
name="email"
type="email"
value={formData.email}
onChange={(e: ChangeEvent<HTMLInputElement>) => setFormData({ ...formData, email: e.target.value })}
error={!!errors.email}
errorMessage={errors.email}
value={formState.fields.email.value}
onChange={handleChange}
error={!!formState.fields.email.error}
errorMessage={formState.fields.email.error}
placeholder="you@example.com"
disabled={loading}
disabled={formState.isSubmitting}
className="pl-10"
autoComplete="email"
/>
@@ -224,13 +235,14 @@ export default function LoginPage() {
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-500" />
<Input
id="password"
name="password"
type={showPassword ? 'text' : 'password'}
value={formData.password}
onChange={(e: ChangeEvent<HTMLInputElement>) => setFormData({ ...formData, password: e.target.value })}
error={!!errors.password}
errorMessage={errors.password}
value={formState.fields.password.value}
onChange={handleChange}
error={!!formState.fields.password.error}
errorMessage={formState.fields.password.error}
placeholder="••••••••"
disabled={loading}
disabled={formState.isSubmitting}
className="pl-10 pr-10"
autoComplete="current-password"
/>
@@ -244,26 +256,44 @@ export default function LoginPage() {
</div>
</div>
{/* Error Message */}
{errors.submit && (
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
className="flex items-start gap-3 p-3 rounded-lg bg-red-500/10 border border-red-500/30"
>
<AlertCircle className="w-5 h-5 text-red-400 flex-shrink-0 mt-0.5" />
<p className="text-sm text-red-400">{errors.submit}</p>
</motion.div>
)}
{/* Remember Me */}
<div className="flex items-center justify-between">
<label className="flex items-center gap-2 cursor-pointer">
<input
id="rememberMe"
name="rememberMe"
type="checkbox"
checked={formState.fields.rememberMe?.value ?? false}
onChange={handleChange}
disabled={formState.isSubmitting}
className="w-4 h-4 rounded border-charcoal-outline bg-iron-gray text-primary-blue focus:ring-primary-blue focus:ring-offset-0"
/>
<span className="text-sm text-gray-300">Keep me signed in</span>
</label>
</div>
{/* Enhanced Error Display */}
<AnimatePresence>
{formState.submitError && (
<EnhancedFormError
error={new Error(formState.submitError)}
onDismiss={() => {
// Clear the error by setting submitError to undefined
setFormState(prev => ({ ...prev, submitError: undefined }));
}}
showDeveloperDetails={showErrorDetails}
/>
)}
</AnimatePresence>
{/* Submit Button */}
<Button
type="submit"
variant="primary"
disabled={loading}
disabled={formState.isSubmitting}
className="w-full flex items-center justify-center gap-2"
>
{loading ? (
{formState.isSubmitting ? (
<>
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
Signing in...
@@ -291,7 +321,7 @@ export default function LoginPage() {
<motion.button
type="button"
onClick={handleDemoLogin}
disabled={loading}
disabled={formState.isSubmitting}
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.99 }}
className="w-full flex items-center justify-center gap-3 px-4 py-3 rounded-lg bg-gradient-to-r from-deep-graphite to-iron-gray border border-charcoal-outline text-gray-300 hover:border-primary-blue/30 transition-all disabled:opacity-50 group"

View File

@@ -19,6 +19,7 @@ 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 {
newPassword?: string;
@@ -49,6 +50,7 @@ function checkPasswordStrength(password: string): PasswordStrength {
export default function ResetPasswordPage() {
const router = useRouter();
const searchParams = useSearchParams();
const { session } = useAuth();
const [loading, setLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false);
@@ -61,6 +63,13 @@ export default function ResetPasswordPage() {
});
const [token, setToken] = useState<string>('');
// Check if user is already authenticated
useEffect(() => {
if (session) {
router.replace('/dashboard');
}
}, [session, router]);
// Extract token from URL on mount
useEffect(() => {
const tokenParam = searchParams.get('token');

View File

@@ -93,7 +93,7 @@ const FEATURES = [
export default function SignupPage() {
const router = useRouter();
const searchParams = useSearchParams();
const { refreshSession } = useAuth();
const { refreshSession, session } = useAuth();
const returnTo = searchParams.get('returnTo') ?? '/onboarding';
const [loading, setLoading] = useState(false);
@@ -111,6 +111,13 @@ export default function SignupPage() {
// 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');
@@ -126,7 +133,7 @@ export default function SignupPage() {
}
}
checkAuth();
}, [router, returnTo]);
}, [session, router, returnTo]);
const passwordStrength = checkPasswordStrength(formData.password);