/** * Error Message Utilities * * Provides user-friendly error message translation and formatting. * Includes helpers for common validation scenarios. */ import { ErrorCodes } from '@/types/errors'; // Generic error messages for common scenarios export const ErrorMessages = { // Network errors networkOffline: 'Unable to connect. Please check your internet connection.', networkTimeout: 'The request took too long. Please try again.', networkError: 'Connection failed. Please check your network.', // Auth errors sessionExpired: 'Your session has expired. Please log in again.', unauthorized: 'You need to log in to continue.', forbidden: 'You don\'t have permission to do this.', // Validation errors required: 'This field is required.', invalidEmail: 'Please enter a valid email address.', invalidPhone: 'Please enter a valid phone number.', invalidOtp: 'Invalid code. Please try again.', tooShort: (min: number) => `Must be at least ${min} characters.`, tooLong: (max: number) => `Must be ${max} characters or less.`, passwordMismatch: 'Passwords do not match.', // Generic errors genericError: 'Something went wrong. Please try again.', tryAgainLater: 'Something went wrong. Please try again later.', notFound: 'The item you\'re looking for could not be found.', // Form errors formInvalid: 'Please check the form for errors.', fileTooBig: (maxMb: number) => `File is too large. Maximum size is ${maxMb}MB.`, invalidFileType: 'This file type is not supported.', // Subscription errors subscriptionRequired: 'A subscription is required to use this feature.', subscriptionExpired: 'Your subscription has expired.', paymentFailed: 'Payment could not be processed. Please try again.', // BLE errors bluetoothDisabled: 'Please enable Bluetooth to continue.', bluetoothPermissionDenied: 'Bluetooth permission is required.', deviceNotFound: 'Device not found. Make sure it\'s powered on.', // Sensor errors sensorSetupFailed: 'Sensor setup failed. Please try again.', sensorOffline: 'Sensor is offline.', wifiConfigFailed: 'Failed to configure WiFi. Check your credentials.', } as const; // Action hints for guiding users export const ActionHints = { checkConnection: 'Check your Wi-Fi or cellular connection', enableBluetooth: 'Enable Bluetooth in Settings', loginAgain: 'Tap here to log in again', contactSupport: 'Contact support if the problem persists', tryDifferentPayment: 'Try a different payment method', refreshPage: 'Pull down to refresh', checkCredentials: 'Double-check your email and password', } as const; /** * Get a user-friendly message for an error code */ export function getErrorMessage(code: string, fallback?: string): string { const messages: Record = { [ErrorCodes.NETWORK_ERROR]: ErrorMessages.networkError, [ErrorCodes.NETWORK_TIMEOUT]: ErrorMessages.networkTimeout, [ErrorCodes.NETWORK_OFFLINE]: ErrorMessages.networkOffline, [ErrorCodes.UNAUTHORIZED]: ErrorMessages.sessionExpired, [ErrorCodes.TOKEN_EXPIRED]: ErrorMessages.sessionExpired, [ErrorCodes.SESSION_EXPIRED]: ErrorMessages.sessionExpired, [ErrorCodes.FORBIDDEN]: ErrorMessages.forbidden, [ErrorCodes.NOT_FOUND]: ErrorMessages.notFound, [ErrorCodes.VALIDATION_ERROR]: ErrorMessages.formInvalid, [ErrorCodes.INVALID_EMAIL]: ErrorMessages.invalidEmail, [ErrorCodes.INVALID_PHONE]: ErrorMessages.invalidPhone, [ErrorCodes.INVALID_OTP]: ErrorMessages.invalidOtp, [ErrorCodes.SUBSCRIPTION_REQUIRED]: ErrorMessages.subscriptionRequired, [ErrorCodes.SUBSCRIPTION_EXPIRED]: ErrorMessages.subscriptionExpired, [ErrorCodes.PAYMENT_FAILED]: ErrorMessages.paymentFailed, [ErrorCodes.BLE_NOT_ENABLED]: ErrorMessages.bluetoothDisabled, [ErrorCodes.BLE_PERMISSION_DENIED]: ErrorMessages.bluetoothPermissionDenied, [ErrorCodes.BLE_DEVICE_NOT_FOUND]: ErrorMessages.deviceNotFound, [ErrorCodes.SENSOR_SETUP_FAILED]: ErrorMessages.sensorSetupFailed, [ErrorCodes.SENSOR_OFFLINE]: ErrorMessages.sensorOffline, [ErrorCodes.SENSOR_WIFI_FAILED]: ErrorMessages.wifiConfigFailed, }; return messages[code] || fallback || ErrorMessages.genericError; } /** * Get an action hint for an error code */ export function getActionHint(code: string): string | undefined { const hints: Record = { [ErrorCodes.NETWORK_ERROR]: ActionHints.checkConnection, [ErrorCodes.NETWORK_OFFLINE]: ActionHints.checkConnection, [ErrorCodes.UNAUTHORIZED]: ActionHints.loginAgain, [ErrorCodes.TOKEN_EXPIRED]: ActionHints.loginAgain, [ErrorCodes.BLE_NOT_ENABLED]: ActionHints.enableBluetooth, [ErrorCodes.BLE_PERMISSION_DENIED]: ActionHints.enableBluetooth, [ErrorCodes.PAYMENT_FAILED]: ActionHints.tryDifferentPayment, }; return hints[code]; } // Validation helpers /** * Validate email format */ export function validateEmail(email: string): string | null { if (!email || email.trim() === '') { return ErrorMessages.required; } const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email.trim())) { return ErrorMessages.invalidEmail; } return null; } /** * Validate phone number format */ export function validatePhone(phone: string): string | null { if (!phone || phone.trim() === '') { return null; // Phone is usually optional } // Remove common formatting characters const cleaned = phone.replace(/[\s\-\(\)\.]/g, ''); // Check for valid phone format (with or without country code) const phoneRegex = /^\+?[1-9]\d{9,14}$/; if (!phoneRegex.test(cleaned)) { return ErrorMessages.invalidPhone; } return null; } /** * Validate required field */ export function validateRequired(value: string | undefined | null, fieldName?: string): string | null { if (!value || value.trim() === '') { return fieldName ? `${fieldName} is required.` : ErrorMessages.required; } return null; } /** * Validate minimum length */ export function validateMinLength(value: string, minLength: number): string | null { if (value.length < minLength) { return ErrorMessages.tooShort(minLength); } return null; } /** * Validate maximum length */ export function validateMaxLength(value: string, maxLength: number): string | null { if (value.length > maxLength) { return ErrorMessages.tooLong(maxLength); } return null; } /** * Validate OTP code format */ export function validateOtp(otp: string, length: number = 6): string | null { if (!otp || otp.trim() === '') { return ErrorMessages.required; } const cleaned = otp.trim(); if (cleaned.length !== length || !/^\d+$/.test(cleaned)) { return ErrorMessages.invalidOtp; } return null; } /** * Format error for display (capitalize first letter, add period if missing) */ export function formatErrorMessage(message: string): string { if (!message) return ErrorMessages.genericError; let formatted = message.trim(); // Capitalize first letter formatted = formatted.charAt(0).toUpperCase() + formatted.slice(1); // Add period if missing if (!formatted.endsWith('.') && !formatted.endsWith('!') && !formatted.endsWith('?')) { formatted += '.'; } return formatted; } /** * Combine multiple field errors into a single message */ export function combineFieldErrors(errors: { field: string; message: string }[]): string { if (errors.length === 0) return ''; if (errors.length === 1) return errors[0].message; return `Please fix ${errors.length} errors: ${errors.map((e) => e.message).join(', ')}`; } /** * Get plural form for error count */ export function getErrorCountText(count: number): string { if (count === 0) return 'No errors'; if (count === 1) return '1 error'; return `${count} errors`; } export default ErrorMessages;