2025-09-20 11:35:40 +09:00
|
|
|
/**
|
|
|
|
|
* Standardized Error Handling for Portal
|
|
|
|
|
* Provides consistent error handling and user-friendly messages
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
export interface ApiError {
|
|
|
|
|
success: false;
|
|
|
|
|
error: {
|
|
|
|
|
code: string;
|
|
|
|
|
message: string;
|
|
|
|
|
details?: Record<string, unknown>;
|
|
|
|
|
};
|
|
|
|
|
timestamp: string;
|
|
|
|
|
path: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ApiErrorInfo {
|
|
|
|
|
code: string;
|
|
|
|
|
message: string;
|
|
|
|
|
shouldLogout?: boolean;
|
|
|
|
|
shouldRetry?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Extract error information from various error types
|
|
|
|
|
*/
|
|
|
|
|
export function getErrorInfo(error: unknown): ApiErrorInfo {
|
|
|
|
|
// Handle API errors with structured format
|
|
|
|
|
if (isApiError(error)) {
|
|
|
|
|
return {
|
|
|
|
|
code: error.error.code,
|
|
|
|
|
message: error.error.message,
|
|
|
|
|
shouldLogout: shouldLogoutForError(error.error.code),
|
|
|
|
|
shouldRetry: shouldRetryForError(error.error.code),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle fetch/network errors
|
|
|
|
|
if (error instanceof Error) {
|
|
|
|
|
if (error.name === 'TypeError' && error.message.includes('fetch')) {
|
|
|
|
|
return {
|
|
|
|
|
code: 'NETWORK_ERROR',
|
|
|
|
|
message: 'Unable to connect to the server. Please check your internet connection and try again.',
|
|
|
|
|
shouldRetry: true,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (error.name === 'AbortError') {
|
|
|
|
|
return {
|
|
|
|
|
code: 'REQUEST_TIMEOUT',
|
|
|
|
|
message: 'The request timed out. Please try again.',
|
|
|
|
|
shouldRetry: true,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
code: 'UNKNOWN_ERROR',
|
|
|
|
|
message: 'An unexpected error occurred. Please try again.',
|
|
|
|
|
shouldRetry: true,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fallback for unknown error types
|
|
|
|
|
return {
|
|
|
|
|
code: 'UNKNOWN_ERROR',
|
|
|
|
|
message: 'An unexpected error occurred. Please try again.',
|
|
|
|
|
shouldRetry: true,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-24 18:00:49 +09:00
|
|
|
export function isApiError(error: unknown): error is ApiError {
|
2025-09-20 11:35:40 +09:00
|
|
|
return (
|
|
|
|
|
typeof error === 'object' &&
|
|
|
|
|
error !== null &&
|
|
|
|
|
'success' in error &&
|
|
|
|
|
error.success === false &&
|
|
|
|
|
'error' in error &&
|
|
|
|
|
typeof (error as any).error === 'object' &&
|
|
|
|
|
'code' in (error as any).error &&
|
|
|
|
|
'message' in (error as any).error
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Determine if the user should be logged out for this error
|
|
|
|
|
*/
|
|
|
|
|
function shouldLogoutForError(code: string): boolean {
|
|
|
|
|
const logoutCodes = [
|
|
|
|
|
'TOKEN_REVOKED',
|
|
|
|
|
'INVALID_REFRESH_TOKEN',
|
|
|
|
|
'UNAUTHORIZED',
|
|
|
|
|
'SESSION_EXPIRED',
|
|
|
|
|
];
|
|
|
|
|
return logoutCodes.includes(code);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Determine if the request should be retried for this error
|
|
|
|
|
*/
|
|
|
|
|
function shouldRetryForError(code: string): boolean {
|
|
|
|
|
const noRetryCodes = [
|
|
|
|
|
'INVALID_CREDENTIALS',
|
|
|
|
|
'FORBIDDEN',
|
|
|
|
|
'ADMIN_REQUIRED',
|
|
|
|
|
'ACCOUNT_LOCKED',
|
|
|
|
|
'VALIDATION_ERROR',
|
|
|
|
|
'ACCOUNT_ALREADY_LINKED',
|
|
|
|
|
'ACCOUNT_EXISTS',
|
|
|
|
|
'CUSTOMER_NOT_FOUND',
|
|
|
|
|
'INVALID_REQUEST',
|
|
|
|
|
];
|
|
|
|
|
return !noRetryCodes.includes(code);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get a user-friendly error message for display in UI
|
|
|
|
|
*/
|
|
|
|
|
export function getUserFriendlyMessage(error: unknown): string {
|
|
|
|
|
const errorInfo = getErrorInfo(error);
|
|
|
|
|
return errorInfo.message;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle authentication errors consistently
|
|
|
|
|
*/
|
|
|
|
|
export function handleAuthError(error: unknown, logout: () => void): void {
|
|
|
|
|
const errorInfo = getErrorInfo(error);
|
|
|
|
|
|
|
|
|
|
if (errorInfo.shouldLogout) {
|
|
|
|
|
logout();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create a standardized error for logging
|
|
|
|
|
*/
|
|
|
|
|
export function createErrorLog(error: unknown, context: string): {
|
|
|
|
|
context: string;
|
|
|
|
|
code: string;
|
|
|
|
|
message: string;
|
|
|
|
|
timestamp: string;
|
|
|
|
|
} {
|
|
|
|
|
const errorInfo = getErrorInfo(error);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
context,
|
|
|
|
|
code: errorInfo.code,
|
|
|
|
|
message: errorInfo.message,
|
|
|
|
|
timestamp: new Date().toISOString(),
|
|
|
|
|
};
|
|
|
|
|
}
|