fix issues
This commit is contained in:
@@ -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 = {};
|
||||
|
||||
|
||||
@@ -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);
|
||||
}, []);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user