Refactor form error handling with type guards, enhance API client configuration, and update implementation plan

- Introduced `isAPIErrorArray` type guard to improve error handling in authentication forms, replacing type assertions for better runtime safety.
- Refactored error handling logic across `RegisterForm`, `LoginForm`, `PasswordResetRequestForm`, and `PasswordResetConfirmForm` for unexpected error fallbacks.
- Updated `next.config.ts` and `.eslintrc.json` to exclude generated API client files from linting and align configuration with latest project structure.
- Added comprehensive documentation on Phase 2 completion in `IMPLEMENTATION_PLAN.md`.
This commit is contained in:
Felipe Cardoso
2025-11-01 01:29:17 +01:00
parent 38eb5313fc
commit c58cce358f
8 changed files with 190 additions and 127 deletions

View File

@@ -16,8 +16,7 @@ import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Alert } from '@/components/ui/alert';
import { useLogin } from '@/lib/api/hooks/useAuth';
import { getGeneralError, getFieldErrors } from '@/lib/api/errors';
import type { APIError } from '@/lib/api/errors';
import { getGeneralError, getFieldErrors, isAPIErrorArray } from '@/lib/api/errors';
import config from '@/config/app.config';
// ============================================================================
@@ -101,22 +100,25 @@ export function LoginForm({
// Success callback
onSuccess?.();
} catch (error) {
// Handle API errors
const errors = error as APIError[];
// Set general error message
const generalError = getGeneralError(errors);
if (generalError) {
setServerError(generalError);
}
// Set field-specific errors
const fieldErrors = getFieldErrors(errors);
Object.entries(fieldErrors).forEach(([field, message]) => {
if (field === 'email' || field === 'password') {
form.setError(field, { message });
// Handle API errors with type guard
if (isAPIErrorArray(error)) {
// Set general error message
const generalError = getGeneralError(error);
if (generalError) {
setServerError(generalError);
}
});
// Set field-specific errors
const fieldErrors = getFieldErrors(error);
Object.entries(fieldErrors).forEach(([field, message]) => {
if (field === 'email' || field === 'password') {
form.setError(field, { message });
}
});
} else {
// Unexpected error format
setServerError('An unexpected error occurred. Please try again.');
}
}
};

View File

@@ -16,8 +16,7 @@ import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Alert } from '@/components/ui/alert';
import { usePasswordResetConfirm } from '@/lib/api/hooks/useAuth';
import { getGeneralError, getFieldErrors } from '@/lib/api/errors';
import type { APIError } from '@/lib/api/errors';
import { getGeneralError, getFieldErrors, isAPIErrorArray } from '@/lib/api/errors';
// ============================================================================
// Validation Schema
@@ -146,22 +145,25 @@ export function PasswordResetConfirmForm({
// Success callback
onSuccess?.();
} catch (error) {
// Handle API errors
const errors = error as APIError[];
// Set general error message
const generalError = getGeneralError(errors);
if (generalError) {
setServerError(generalError);
}
// Set field-specific errors
const fieldErrors = getFieldErrors(errors);
Object.entries(fieldErrors).forEach(([field, message]) => {
if (field === 'token' || field === 'new_password') {
form.setError(field, { message });
// Handle API errors with type guard
if (isAPIErrorArray(error)) {
// Set general error message
const generalError = getGeneralError(error);
if (generalError) {
setServerError(generalError);
}
});
// Set field-specific errors
const fieldErrors = getFieldErrors(error);
Object.entries(fieldErrors).forEach(([field, message]) => {
if (field === 'token' || field === 'new_password') {
form.setError(field, { message });
}
});
} else {
// Unexpected error format
setServerError('An unexpected error occurred. Please try again.');
}
}
};

View File

@@ -16,8 +16,7 @@ import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Alert } from '@/components/ui/alert';
import { usePasswordResetRequest } from '@/lib/api/hooks/useAuth';
import { getGeneralError, getFieldErrors } from '@/lib/api/errors';
import type { APIError } from '@/lib/api/errors';
import { getGeneralError, getFieldErrors, isAPIErrorArray } from '@/lib/api/errors';
// ============================================================================
// Validation Schema
@@ -100,22 +99,25 @@ export function PasswordResetRequestForm({
// Success callback
onSuccess?.();
} catch (error) {
// Handle API errors
const errors = error as APIError[];
// Set general error message
const generalError = getGeneralError(errors);
if (generalError) {
setServerError(generalError);
}
// Set field-specific errors
const fieldErrors = getFieldErrors(errors);
Object.entries(fieldErrors).forEach(([field, message]) => {
if (field === 'email') {
form.setError(field, { message });
// Handle API errors with type guard
if (isAPIErrorArray(error)) {
// Set general error message
const generalError = getGeneralError(error);
if (generalError) {
setServerError(generalError);
}
});
// Set field-specific errors
const fieldErrors = getFieldErrors(error);
Object.entries(fieldErrors).forEach(([field, message]) => {
if (field === 'email') {
form.setError(field, { message });
}
});
} else {
// Unexpected error format
setServerError('An unexpected error occurred. Please try again.');
}
}
};

View File

@@ -16,8 +16,7 @@ import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Alert } from '@/components/ui/alert';
import { useRegister } from '@/lib/api/hooks/useAuth';
import { getGeneralError, getFieldErrors } from '@/lib/api/errors';
import type { APIError } from '@/lib/api/errors';
import { getGeneralError, getFieldErrors, isAPIErrorArray } from '@/lib/api/errors';
import config from '@/config/app.config';
// ============================================================================
@@ -124,22 +123,25 @@ export function RegisterForm({
// Success callback
onSuccess?.();
} catch (error) {
// Handle API errors
const errors = error as APIError[];
// Set general error message
const generalError = getGeneralError(errors);
if (generalError) {
setServerError(generalError);
}
// Set field-specific errors
const fieldErrors = getFieldErrors(errors);
Object.entries(fieldErrors).forEach(([field, message]) => {
if (field in form.getValues()) {
form.setError(field as keyof RegisterFormData, { message });
// Handle API errors with type guard
if (isAPIErrorArray(error)) {
// Set general error message
const generalError = getGeneralError(error);
if (generalError) {
setServerError(generalError);
}
});
// Set field-specific errors
const fieldErrors = getFieldErrors(error);
Object.entries(fieldErrors).forEach(([field, message]) => {
if (field in form.getValues()) {
form.setError(field as keyof RegisterFormData, { message });
}
});
} else {
// Unexpected error format
setServerError('An unexpected error occurred. Please try again.');
}
}
};

View File

@@ -172,3 +172,28 @@ export function getGeneralError(errors: APIError[]): string | undefined {
const generalError = errors.find((error) => !error.field);
return generalError ? generalError.message || getErrorMessage(generalError.code) : undefined;
}
/**
* Type guard to check if error is an APIError array
* Provides runtime type safety instead of type assertions
* @param error Unknown error object
* @returns true if error is APIError[]
*/
export function isAPIErrorArray(error: unknown): error is APIError[] {
if (!Array.isArray(error)) {
return false;
}
// Check if all elements match APIError structure
return error.every(
(e) =>
typeof e === 'object' &&
e !== null &&
'code' in e &&
'message' in e &&
typeof e.code === 'string' &&
typeof e.message === 'string' &&
// field is optional
(!('field' in e) || typeof e.field === 'string')
);
}