diff --git a/lib/core/network/api_client.dart b/lib/core/network/api_client.dart index 1916d2e..3f572b5 100644 --- a/lib/core/network/api_client.dart +++ b/lib/core/network/api_client.dart @@ -231,40 +231,94 @@ class ApiClient { /// Wrap a DioException with parsed error data. DioException _wrapError(DioException error) { final response = error.response; + + // 1. Try to parse a structured error from the JSON response body if (response != null) { final data = response.data; if (data is Map) { - final message = data['message'] is List - ? (data['message'] as List).join(', ') - : data['message']?.toString() ?? 'An error occurred'; + final rawMessage = data['message'] ?? data['error']; + final message = rawMessage is List + ? (rawMessage).join(', ') + : rawMessage?.toString(); - final errors = (data['errors'] as List?) - ?.map( - (e) => FieldError.fromJson(e as Map)) - .toList() ?? - []; + if (message != null && message.isNotEmpty) { + final errors = (data['errors'] as List?) + ?.map((e) => FieldError.fromJson(e as Map)) + .toList() ?? + []; - return DioException( - requestOptions: error.requestOptions, - response: error.response, - error: ApiException( - message: message, - statusCode: response.statusCode, - fieldErrors: errors, - ), - ); + return DioException( + requestOptions: error.requestOptions, + response: error.response, + error: ApiException( + message: message, + statusCode: response.statusCode, + fieldErrors: errors, + ), + ); + } } } + // 2. Fallback — build a user-friendly message from the status code or + // Dio exception type. NEVER use `error.message` because Dio emits a + // giant technical blob ("This exception was thrown because the + // response has a status code of 401 and RequestOptions.validateStatus + // was configured to throw...") which is useless to end users. return DioException( requestOptions: error.requestOptions, response: error.response, error: ApiException( - message: error.message ?? 'Network error occurred', + message: _friendlyMessageFor(error), statusCode: response?.statusCode, ), ); } + + String _friendlyMessageFor(DioException error) { + // Network-level failures take priority over status codes + switch (error.type) { + case DioExceptionType.connectionTimeout: + case DioExceptionType.sendTimeout: + case DioExceptionType.receiveTimeout: + return 'The request timed out. Please check your connection and try again.'; + case DioExceptionType.connectionError: + return 'Unable to reach the server. Please check your internet connection.'; + case DioExceptionType.cancel: + return 'Request was cancelled.'; + case DioExceptionType.badCertificate: + return 'Secure connection failed. Please try again later.'; + case DioExceptionType.badResponse: + case DioExceptionType.unknown: + break; + } + + // Status-code-based fallback + final code = error.response?.statusCode; + switch (code) { + case 400: + return 'The request was invalid. Please check your input.'; + case 401: + return 'Invalid email or password.'; + case 403: + return 'You don\'t have permission to perform this action.'; + case 404: + return 'The requested resource was not found.'; + case 409: + return 'This action conflicts with existing data.'; + case 422: + return 'Some fields are invalid. Please check your input.'; + case 429: + return 'Too many attempts. Please wait a moment and try again.'; + case 500: + case 502: + case 503: + case 504: + return 'Server is temporarily unavailable. Please try again shortly.'; + } + + return 'Something went wrong. Please try again.'; + } } class _QueuedRequest {