Some checks failed
ParentFlow CI/CD Pipeline / Backend Tests (push) Has been cancelled
ParentFlow CI/CD Pipeline / Frontend Tests (push) Has been cancelled
ParentFlow CI/CD Pipeline / Security Scanning (push) Has been cancelled
ParentFlow CI/CD Pipeline / Build Docker Images (map[context:maternal-app/maternal-app-backend dockerfile:Dockerfile.production name:backend]) (push) Has been cancelled
ParentFlow CI/CD Pipeline / Build Docker Images (map[context:maternal-web dockerfile:Dockerfile.production name:frontend]) (push) Has been cancelled
ParentFlow CI/CD Pipeline / Deploy to Development (push) Has been cancelled
ParentFlow CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / Lint and Test (push) Has been cancelled
CI/CD Pipeline / E2E Tests (push) Has been cancelled
CI/CD Pipeline / Build Application (push) Has been cancelled
## Error Handling System - Add centralized error handling utilities (errorHandler.ts) - Create reusable error components (ErrorMessage, ErrorToast) - Implement multilingual error support (preserves backend error messages in 5 languages) - Update 15+ forms and components with consistent error handling - Auth forms: login, register, forgot-password - Family management: family page, join family dialog - Child management: child dialog - All tracking forms: feeding, sleep, diaper, medicine, growth, activity ## Production Build Fixes - Fix backend TypeScript errors: InviteCode.uses → InviteCode.useCount (5 instances) - Remove non-existent savedFamily variable from registration response - Fix admin panel TypeScript errors: SimpleMDE toolbar type, PieChart label type ## User Experience Improvements - Auto-uppercase invite code and share code inputs - Visual feedback for case conversion with helper text - Improved form validation with error codes ## CI/CD Pipeline - Create comprehensive production deployment checklist (PRODUCTION_DEPLOYMENT_CHECKLIST.md) - Add automated pre-deployment check script (pre-deploy-check.sh) - Validates frontend, backend, and admin panel builds - Checks git status, branch, and sync state - Verifies environment files and migrations - Add quick start deployment guide (DEPLOYMENT_QUICK_START.md) - Add production deployment automation template (deploy-production.sh) ## Cleanup - Remove outdated push notifications documentation files - Remove outdated PWA implementation plan 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
268 lines
6.7 KiB
TypeScript
268 lines
6.7 KiB
TypeScript
/**
|
|
* Error Handler Utility
|
|
* Provides centralized error handling with multilingual support
|
|
* Preserves backend error messages which are already localized in 5 languages:
|
|
* - English (en)
|
|
* - Spanish (es)
|
|
* - French (fr)
|
|
* - Portuguese (pt)
|
|
* - Chinese (zh)
|
|
*/
|
|
|
|
export interface ErrorResponse {
|
|
code: string;
|
|
message: string;
|
|
field?: string;
|
|
details?: any;
|
|
}
|
|
|
|
export interface ExtractedError {
|
|
code: string;
|
|
message: string;
|
|
field?: string;
|
|
details?: any;
|
|
isBackendError: boolean;
|
|
}
|
|
|
|
/**
|
|
* Extract error information from various error types
|
|
* Prioritizes backend error messages to preserve multilingual support
|
|
*/
|
|
export function extractError(error: any): ExtractedError {
|
|
// Default error
|
|
const defaultError: ExtractedError = {
|
|
code: 'UNKNOWN_ERROR',
|
|
message: 'An unexpected error occurred. Please try again.',
|
|
isBackendError: false,
|
|
};
|
|
|
|
// If no error, return default
|
|
if (!error) {
|
|
return defaultError;
|
|
}
|
|
|
|
// Axios error response with backend error
|
|
if (error.response?.data?.error) {
|
|
const backendError = error.response.data.error;
|
|
return {
|
|
code: backendError.code || 'BACKEND_ERROR',
|
|
message: backendError.message || defaultError.message,
|
|
field: backendError.field,
|
|
details: backendError.details,
|
|
isBackendError: true,
|
|
};
|
|
}
|
|
|
|
// Axios error response with message
|
|
if (error.response?.data?.message) {
|
|
return {
|
|
code: error.response.data.code || 'BACKEND_ERROR',
|
|
message: error.response.data.message,
|
|
field: error.response.data.field,
|
|
details: error.response.data.details,
|
|
isBackendError: true,
|
|
};
|
|
}
|
|
|
|
// Network errors
|
|
if (error.code === 'ERR_NETWORK' || error.message === 'Network Error') {
|
|
return {
|
|
code: 'NETWORK_ERROR',
|
|
message: 'Unable to connect to the server. Please check your internet connection.',
|
|
isBackendError: false,
|
|
};
|
|
}
|
|
|
|
// Timeout errors
|
|
if (error.code === 'ECONNABORTED' || error.message?.includes('timeout')) {
|
|
return {
|
|
code: 'TIMEOUT_ERROR',
|
|
message: 'Request timed out. Please try again.',
|
|
isBackendError: false,
|
|
};
|
|
}
|
|
|
|
// HTTP status code errors
|
|
if (error.response?.status) {
|
|
const status = error.response.status;
|
|
|
|
if (status === 401) {
|
|
return {
|
|
code: 'UNAUTHORIZED',
|
|
message: 'Your session has expired. Please log in again.',
|
|
isBackendError: false,
|
|
};
|
|
}
|
|
|
|
if (status === 403) {
|
|
return {
|
|
code: 'FORBIDDEN',
|
|
message: 'You do not have permission to perform this action.',
|
|
isBackendError: false,
|
|
};
|
|
}
|
|
|
|
if (status === 404) {
|
|
return {
|
|
code: 'NOT_FOUND',
|
|
message: 'The requested resource was not found.',
|
|
isBackendError: false,
|
|
};
|
|
}
|
|
|
|
if (status === 429) {
|
|
return {
|
|
code: 'RATE_LIMIT_EXCEEDED',
|
|
message: 'Too many requests. Please try again later.',
|
|
isBackendError: false,
|
|
};
|
|
}
|
|
|
|
if (status >= 500) {
|
|
return {
|
|
code: 'SERVER_ERROR',
|
|
message: 'A server error occurred. Please try again later.',
|
|
isBackendError: false,
|
|
};
|
|
}
|
|
}
|
|
|
|
// Generic error with message
|
|
if (error.message) {
|
|
return {
|
|
code: error.code || 'ERROR',
|
|
message: error.message,
|
|
isBackendError: false,
|
|
};
|
|
}
|
|
|
|
// String error
|
|
if (typeof error === 'string') {
|
|
return {
|
|
code: 'ERROR',
|
|
message: error,
|
|
isBackendError: false,
|
|
};
|
|
}
|
|
|
|
return defaultError;
|
|
}
|
|
|
|
/**
|
|
* Format error message for display
|
|
* Preserves backend messages (which are already localized)
|
|
*/
|
|
export function formatErrorMessage(error: any): string {
|
|
const extracted = extractError(error);
|
|
return extracted.message;
|
|
}
|
|
|
|
/**
|
|
* Get user-friendly error message based on error code
|
|
* Only used for client-side errors; backend errors use their own messages
|
|
*/
|
|
export function getUserFriendlyMessage(errorCode: string): string {
|
|
const messages: Record<string, string> = {
|
|
// Network errors
|
|
NETWORK_ERROR: 'Unable to connect to the server. Please check your internet connection.',
|
|
TIMEOUT_ERROR: 'Request timed out. Please try again.',
|
|
|
|
// Authentication errors
|
|
UNAUTHORIZED: 'Your session has expired. Please log in again.',
|
|
FORBIDDEN: 'You do not have permission to perform this action.',
|
|
|
|
// HTTP errors
|
|
NOT_FOUND: 'The requested resource was not found.',
|
|
RATE_LIMIT_EXCEEDED: 'Too many requests. Please try again later.',
|
|
SERVER_ERROR: 'A server error occurred. Please try again later.',
|
|
|
|
// Generic errors
|
|
UNKNOWN_ERROR: 'An unexpected error occurred. Please try again.',
|
|
ERROR: 'An error occurred. Please try again.',
|
|
};
|
|
|
|
return messages[errorCode] || messages.UNKNOWN_ERROR;
|
|
}
|
|
|
|
/**
|
|
* Check if error is a specific type
|
|
*/
|
|
export function isErrorType(error: any, errorCode: string): boolean {
|
|
const extracted = extractError(error);
|
|
return extracted.code === errorCode;
|
|
}
|
|
|
|
/**
|
|
* Check if error is a network error
|
|
*/
|
|
export function isNetworkError(error: any): boolean {
|
|
return isErrorType(error, 'NETWORK_ERROR');
|
|
}
|
|
|
|
/**
|
|
* Check if error is an authentication error
|
|
*/
|
|
export function isAuthError(error: any): boolean {
|
|
return isErrorType(error, 'UNAUTHORIZED') || isErrorType(error, 'AUTH_TOKEN_EXPIRED');
|
|
}
|
|
|
|
/**
|
|
* Check if error is a validation error
|
|
*/
|
|
export function isValidationError(error: any): boolean {
|
|
const extracted = extractError(error);
|
|
return extracted.code.startsWith('VALIDATION_') || extracted.field !== undefined;
|
|
}
|
|
|
|
/**
|
|
* Get field-specific error message
|
|
*/
|
|
export function getFieldError(error: any, fieldName: string): string | null {
|
|
const extracted = extractError(error);
|
|
|
|
if (extracted.field === fieldName) {
|
|
return extracted.message;
|
|
}
|
|
|
|
if (extracted.details && typeof extracted.details === 'object') {
|
|
return extracted.details[fieldName] || null;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Log error for debugging (can be extended with error tracking service)
|
|
*/
|
|
export function logError(error: any, context?: string): void {
|
|
const extracted = extractError(error);
|
|
|
|
console.error('[Error Handler]', {
|
|
context,
|
|
code: extracted.code,
|
|
message: extracted.message,
|
|
field: extracted.field,
|
|
details: extracted.details,
|
|
isBackendError: extracted.isBackendError,
|
|
originalError: error,
|
|
});
|
|
|
|
// TODO: Send to error tracking service (Sentry, LogRocket, etc.)
|
|
// Example:
|
|
// if (window.Sentry) {
|
|
// window.Sentry.captureException(error, {
|
|
// tags: { code: extracted.code },
|
|
// contexts: { errorHandler: { context } },
|
|
// });
|
|
// }
|
|
}
|
|
|
|
/**
|
|
* Handle error and return user-friendly message
|
|
* Main function to use in catch blocks
|
|
*/
|
|
export function handleError(error: any, context?: string): string {
|
|
logError(error, context);
|
|
return formatErrorMessage(error);
|
|
}
|