diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx index 451ef7c4..fe7eaf11 100644 --- a/frontend/src/pages/Login.tsx +++ b/frontend/src/pages/Login.tsx @@ -10,11 +10,6 @@ interface ValidationError { message: string } -interface PydanticValidationError { - loc?: Array - msg?: string -} - export default function Login() { const navigate = useNavigate() const { setAuth } = useAuthStore() @@ -24,6 +19,10 @@ export default function Login() { const [errors, setErrors] = useState([]) const [loading, setLoading] = useState(false) + const setError = (message: string) => { + setErrors([{ field: 'general', message }]) + } + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() setErrors([]) @@ -47,42 +46,25 @@ export default function Login() { setLoading(true) try { - const tokenData = await authApi.login(trimmedEmail, password) + const tokenData = await authApi.login(email, password) setAuth(tokenData.access_token, null) - const user = await authApi.getMe(tokenData.access_token) + const user = await authApi.getMe() setAuth(tokenData.access_token, user) navigate('/') } catch (err) { if (axios.isAxiosError(err)) { - const detail = err.response?.data?.detail - if (detail) { - if (typeof detail === 'object' && detail.field && detail.message) { - // Authentication failure (always field: 'general') - setErrors([{ field: detail.field, message: detail.message }]) - } else if (Array.isArray(detail)) { - // Pydantic 422 errors — login uses OAuth2PasswordRequestForm so - // the server field name is 'username'; map it to 'email' for the UI. - const parsed = detail.map((error: PydanticValidationError) => { - const rawField = String(error.loc?.[error.loc.length - 1] ?? 'general') - const field = rawField === 'username' ? 'email' : rawField - return { field, message: error.msg || 'Invalid input' } - }) - setErrors(parsed) - } else { - setErrors([{ field: 'general', message: String(detail) }]) - } - } else if (err.code === 'ERR_NETWORK') { - setErrors([ - { - field: 'general', - message: 'Network error. Please check your connection and try again.', - }, - ]) + const status = err.response?.status + if (status === 401 || status === 403) { + setError('Invalid email or password.') + } else if (status && status >= 500) { + setError('Server error, please try again later.') + } else if (!err.response) { + setError('Unable to connect to server.') } else { - setErrors([{ field: 'general', message: 'Invalid email or password' }]) + setError(err.response.data?.detail ?? 'Something went wrong. Please try again.') } } else { - setErrors([{ field: 'general', message: 'An unexpected error occurred. Please try again.' }]) + setError('Something went wrong. Please try again.') } } finally { setLoading(false) @@ -103,11 +85,11 @@ export default function Login() {
- {errors.some((e: any) => e.field === 'general') && ( + {errors.some((e: ValidationError) => e.field === 'general') && (
- {errors.find((e: any) => e.field === 'general')?.message} + {errors.find((e: ValidationError) => e.field === 'general')?.message}
)} @@ -122,15 +104,15 @@ export default function Login() { required value={email} onChange={(e) => setEmail(e.target.value)} - className={`mt-1 block w-full px-3 py-2 border rounded-lg shadow-sm focus:ring-primary-500 focus:border-primary-500 ${ - errors.some((e: any) => e.field === 'email') + className={`mt-1 block w-full px-3 py-2 border rounded-lg shadow-sm focus:ring-primary-500 focus:border-primary-500${ + errors.some((e: ValidationError) => e.field === 'email') ? 'border-red-300 bg-red-50' : 'border-gray-300' }`} /> - {errors.some((e: any) => e.field === 'email') && ( + {errors.some((e: ValidationError) => e.field === 'email') && (

- {errors.find((e: any) => e.field === 'email')?.message} + {errors.find((e: ValidationError) => e.field === 'email')?.message}

)} @@ -147,7 +129,7 @@ export default function Login() { value={password} onChange={(e) => setPassword(e.target.value)} className={`block w-full pl-3 pr-10 py-2 border rounded-lg shadow-sm focus:ring-primary-500 focus:border-primary-500 ${ - errors.some((e: any) => e.field === 'password') + errors.some((e: ValidationError) => e.field === 'password') ? 'border-red-300 bg-red-50' : 'border-gray-300' }`} @@ -160,9 +142,9 @@ export default function Login() { {showPassword ? : } - {errors.some((e: any) => e.field === 'password') && ( + {errors.some((e: ValidationError) => e.field === 'password') && (

- {errors.find((e: any) => e.field === 'password')?.message} + {errors.find((e: ValidationError) => e.field === 'password')?.message}

)} @@ -185,4 +167,4 @@ export default function Login() { ) -} +} \ No newline at end of file diff --git a/frontend/src/pages/Register.tsx b/frontend/src/pages/Register.tsx index a4af9c58..93d4a688 100644 --- a/frontend/src/pages/Register.tsx +++ b/frontend/src/pages/Register.tsx @@ -138,7 +138,6 @@ export default function Register() { setLoading(false) } } - return (
@@ -153,11 +152,11 @@ export default function Register() {
- {errors.some((e: any) => e.field === 'general') && ( + {errors.some((e: ValidationError) => e.field === 'general') && (
- {errors.find((e: any) => e.field === 'general')?.message} + {errors.find((e: ValidationError) => e.field === 'general')?.message}
)} @@ -173,14 +172,14 @@ export default function Register() { value={formData.email} onChange={(e: React.ChangeEvent) => setFormData({ ...formData, email: e.target.value })} className={`mt-1 block w-full px-3 py-2 border rounded-lg shadow-sm focus:ring-primary-500 focus:border-primary-500 ${ - errors.some((e: any) => e.field === 'email') + errors.some((e: ValidationError) => e.field === 'email') ? 'border-red-300 bg-red-50' : 'border-gray-300' }`} /> - {errors.some((e: any) => e.field === 'email') && ( + {errors.some((e: ValidationError) => e.field === 'email') && (

- {errors.find((e: any) => e.field === 'email')?.message} + {errors.find((e: ValidationError) => e.field === 'email')?.message}

)}
@@ -199,7 +198,7 @@ export default function Register() { onFocus={() => setShowPasswordRequirements(true)} onBlur={() => setShowPasswordRequirements(false)} className={`block w-full pl-3 pr-10 py-2 border rounded-lg shadow-sm focus:ring-primary-500 focus:border-primary-500 ${ - errors.some((e: any) => e.field === 'password') + errors.some((e: ValidationError) => e.field === 'password') ? 'border-red-300 bg-red-50' : 'border-gray-300' }`} @@ -241,9 +240,9 @@ export default function Register() { )} - {errors.some((e: any) => e.field === 'password') && ( + {errors.some((e: ValidationError) => e.field === 'password') && (

- {errors.find((e: any) => e.field === 'password')?.message} + {errors.find((e: ValidationError) => e.field === 'password')?.message}

)} @@ -259,14 +258,14 @@ export default function Register() { value={formData.full_name} onChange={(e: React.ChangeEvent) => setFormData({ ...formData, full_name: e.target.value })} className={`mt-1 block w-full px-3 py-2 border rounded-lg shadow-sm focus:ring-primary-500 focus:border-primary-500 ${ - errors.some((e: any) => e.field === 'full_name') + errors.some((e: ValidationError) => e.field === 'full_name') ? 'border-red-300 bg-red-50' : 'border-gray-300' }`} /> - {errors.some((e: any) => e.field === 'full_name') && ( + {errors.some((e: ValidationError) => e.field === 'full_name') && (

- {errors.find((e: any) => e.field === 'full_name')?.message} + {errors.find((e: ValidationError) => e.field === 'full_name')?.message}

)} @@ -282,14 +281,14 @@ export default function Register() { value={formData.company_name} onChange={(e: React.ChangeEvent) => setFormData({ ...formData, company_name: e.target.value })} className={`mt-1 block w-full px-3 py-2 border rounded-lg shadow-sm focus:ring-primary-500 focus:border-primary-500 ${ - errors.some((e: any) => e.field === 'company_name') + errors.some((e: ValidationError) => e.field === 'company_name') ? 'border-red-300 bg-red-50' : 'border-gray-300' }`} /> - {errors.some((e: any) => e.field === 'company_name') && ( + {errors.some((e: ValidationError) => e.field === 'company_name') && (

- {errors.find((e: any) => e.field === 'company_name')?.message} + {errors.find((e: ValidationError) => e.field === 'company_name')?.message}

)}