feat: Implement comprehensive error handling and production deployment pipeline
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>
This commit is contained in:
Andrei
2025-10-09 21:27:39 +00:00
parent 40dbb2287a
commit c22fa82521
29 changed files with 1810 additions and 2130 deletions

View File

@@ -1,5 +1,6 @@
import axios from 'axios';
import { tokenStorage } from '@/lib/utils/tokenStorage';
import { logError } from '@/lib/utils/errorHandler';
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3020';
@@ -25,12 +26,15 @@ apiClient.interceptors.request.use(
}
);
// Response interceptor to handle token refresh
// Response interceptor to handle token refresh and error logging
apiClient.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
// Log all API errors for debugging and error tracking
logError(error, `API ${originalRequest?.method?.toUpperCase()} ${originalRequest?.url}`);
// Only handle token refresh on client side
if (typeof window === 'undefined') {
return Promise.reject(error);

View File

@@ -4,6 +4,7 @@ import { createContext, useContext, useEffect, useState, ReactNode } from 'react
import { useRouter } from 'next/navigation';
import apiClient from '@/lib/api/client';
import { tokenStorage } from '@/lib/utils/tokenStorage';
import { handleError, formatErrorMessage } from '@/lib/utils/errorHandler';
export interface User {
id: string;
@@ -214,8 +215,8 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
router.push('/');
} catch (error: any) {
console.error('Login failed:', error);
throw new Error(error.response?.data?.message || 'Login failed');
const errorMessage = handleError(error, 'AuthContext.login');
throw new Error(errorMessage);
}
};
@@ -285,8 +286,8 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
// Redirect to onboarding
router.push('/onboarding');
} catch (error: any) {
console.error('Registration failed:', error);
throw new Error(error.response?.data?.message || error.message || 'Registration failed');
const errorMessage = handleError(error, 'AuthContext.register');
throw new Error(errorMessage);
}
};

View File

@@ -0,0 +1,267 @@
/**
* 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);
}