import 'package:dio/dio.dart'; import 'package:real_estate_mobile/core/network/api_client.dart'; import 'package:real_estate_mobile/core/network/api_exceptions.dart'; class ProfileRepository { final Dio _dio = ApiClient.instance.dio; /// Fetch profile based on role. /// Agent: GET /agents/profile/me /// User: GET /users/profile/me Future> getMyProfile(String role) async { try { final endpoint = role == 'AGENT' ? '/agents/profile/me' : '/users/profile/me'; final response = await _dio.get(endpoint); return response.data['data'] as Map; } on DioException catch (e) { if (e.error is ApiException) throw e.error as ApiException; throw ApiException( message: e.message ?? 'Failed to fetch profile', statusCode: e.response?.statusCode, ); } } /// Update profile based on role. /// Agent: PUT /agents/profile /// User: PATCH /users/profile/me Future> updateProfile( String role, Map data, ) async { try { final Response response; if (role == 'AGENT') { response = await _dio.put('/agents/profile', data: data); } else { response = await _dio.patch('/users/profile/me', data: data); } return response.data['data'] as Map; } on DioException catch (e) { if (e.error is ApiException) throw e.error as ApiException; throw ApiException( message: e.message ?? 'Failed to update profile', statusCode: e.response?.statusCode, ); } } /// Get presigned upload URL for avatar. /// Returns { uploadUrl: String, key: String } Future> getAvatarUploadUrl( String role, String fileName, String contentType) async { try { final endpoint = role == 'AGENT' ? '/upload/avatar-presigned-url' : '/upload/user-avatar-presigned-url'; final response = await _dio.post(endpoint, data: { 'filename': fileName, 'contentType': contentType, }); final data = response.data['data'] as Map; return { 'uploadUrl': data['uploadUrl'] as String, 'key': data['key'] as String, }; } on DioException catch (e) { if (e.error is ApiException) throw e.error as ApiException; throw ApiException( message: e.message ?? 'Failed to get upload URL', statusCode: e.response?.statusCode, ); } } /// Upload file bytes directly to S3 presigned URL. Future uploadToS3( String uploadUrl, List fileBytes, String contentType) async { try { await Dio().put( uploadUrl, data: Stream.fromIterable(fileBytes.map((e) => [e])), options: Options( headers: { 'Content-Type': contentType, 'Content-Length': fileBytes.length, }, ), ); } on DioException catch (e) { throw ApiException( message: e.message ?? 'Failed to upload file', statusCode: e.response?.statusCode, ); } } /// Get presigned upload URL for documents. /// Returns { uploadUrl: String, key: String, publicUrl: String } Future> getDocumentUploadUrl( String fileName, String contentType) async { try { final response = await _dio.post('/upload/presigned-url', data: { 'filename': fileName, 'contentType': contentType, }); final data = response.data['data'] as Map; return { 'uploadUrl': data['uploadUrl'] as String, 'key': data['key'] as String, 'publicUrl': (data['publicUrl'] ?? data['key'] ?? '') as String, }; } on DioException catch (e) { if (e.error is ApiException) throw e.error as ApiException; throw ApiException( message: e.message ?? 'Failed to get document upload URL', statusCode: e.response?.statusCode, ); } } /// Delete avatar from S3: DELETE /upload/{encodedKey} Future deleteAvatar(String key) async { try { final encodedKey = Uri.encodeComponent(key); await _dio.delete('/upload/$encodedKey'); } on DioException catch (_) { // S3 delete failure is non-critical } } /// Fetch privacy preferences. /// Agent: GET /agents/preferences/privacy /// User: GET /users/preferences/privacy Future> getPrivacyPreferences(String role) async { try { final endpoint = role == 'AGENT' ? '/agents/preferences/privacy' : '/users/preferences/privacy'; final response = await _dio.get(endpoint); return response.data['data'] as Map; } on DioException catch (e) { if (e.error is ApiException) throw e.error as ApiException; throw ApiException( message: e.message ?? 'Failed to fetch privacy preferences', statusCode: e.response?.statusCode, ); } } /// Update privacy preferences. /// Agent: PUT /agents/preferences/privacy /// User: PUT /users/preferences/privacy Future> updatePrivacyPreferences( String role, Map data, ) async { try { final endpoint = role == 'AGENT' ? '/agents/preferences/privacy' : '/users/preferences/privacy'; final response = await _dio.put(endpoint, data: data); return response.data['data'] as Map; } on DioException catch (e) { if (e.error is ApiException) throw e.error as ApiException; throw ApiException( message: e.message ?? 'Failed to update privacy preferences', statusCode: e.response?.statusCode, ); } } /// Delete account. /// Agent: DELETE /agents/account /// User: DELETE /users/account Future deleteAccount(String role) async { try { final endpoint = role == 'AGENT' ? '/agents/account' : '/users/account'; await _dio.delete(endpoint); } on DioException catch (e) { if (e.error is ApiException) throw e.error as ApiException; throw ApiException( message: e.message ?? 'Failed to delete account', statusCode: e.response?.statusCode, ); } } /// Change password: POST /auth/change-password Future changePassword( String currentPassword, String newPassword) async { try { await _dio.post('/auth/change-password', data: { 'currentPassword': currentPassword, 'newPassword': newPassword, }); } on DioException catch (e) { if (e.error is ApiException) throw e.error as ApiException; throw ApiException( message: e.response?.data?['message'] ?? e.message ?? 'Failed to change password', statusCode: e.response?.statusCode, ); } } /// Fetch notification preferences. /// Agent: GET /agents/preferences/notifications /// User: GET /users/preferences/notifications Future> getNotificationPreferences(String role) async { try { final endpoint = role == 'AGENT' ? '/agents/preferences/notifications' : '/users/preferences/notifications'; final response = await _dio.get(endpoint); return response.data['data'] as Map; } on DioException catch (e) { if (e.error is ApiException) throw e.error as ApiException; throw ApiException( message: e.message ?? 'Failed to fetch notification preferences', statusCode: e.response?.statusCode, ); } } /// Update notification preferences. /// Agent: PUT /agents/preferences/notifications /// User: PUT /users/preferences/notifications Future> updateNotificationPreferences( String role, Map data, ) async { try { final endpoint = role == 'AGENT' ? '/agents/preferences/notifications' : '/users/preferences/notifications'; final response = await _dio.put(endpoint, data: data); return response.data['data'] as Map; } on DioException catch (e) { if (e.error is ApiException) throw e.error as ApiException; throw ApiException( message: e.message ?? 'Failed to update notification preferences', statusCode: e.response?.statusCode, ); } } /// Generate testimonial link: POST /testimonials/generate-link Future generateTestimonialLink() async { try { final response = await _dio.post('/testimonials/generate-link'); return response.data['data']['token'] as String; } on DioException catch (e) { if (e.error is ApiException) throw e.error as ApiException; throw ApiException( message: e.message ?? 'Failed to generate testimonial link', statusCode: e.response?.statusCode, ); } } /// Get agent's testimonials: GET /testimonials/my?sort=latest|oldest Future>> getMyTestimonials( {String sort = 'latest'}) async { try { final response = await _dio.get('/testimonials/my', queryParameters: {'sort': sort}); final list = response.data['data'] as List; return list.cast>(); } on DioException catch (e) { if (e.error is ApiException) throw e.error as ApiException; throw ApiException( message: e.message ?? 'Failed to fetch testimonials', statusCode: e.response?.statusCode, ); } } /// Get current subscription: GET /stripe/subscription Future?> getSubscription() async { try { final response = await _dio.get('/stripe/subscription'); return response.data['data'] as Map?; } on DioException catch (e) { if (e.response?.statusCode == 404) return null; if (e.error is ApiException) throw e.error as ApiException; throw ApiException( message: e.message ?? 'Failed to fetch subscription', statusCode: e.response?.statusCode, ); } } /// Create Stripe checkout session: POST /stripe/create-checkout-session Future createCheckoutSession(String planId) async { try { final response = await _dio.post('/stripe/create-checkout-session', data: {'planId': planId}); return response.data['data']['url'] as String; } on DioException catch (e) { if (e.error is ApiException) throw e.error as ApiException; throw ApiException( message: e.message ?? 'Failed to create checkout session', statusCode: e.response?.statusCode, ); } } /// Create Stripe billing portal: POST /stripe/create-portal-session Future createPortalSession() async { try { final response = await _dio.post('/stripe/create-portal-session'); return response.data['data']['url'] as String; } on DioException catch (e) { if (e.error is ApiException) throw e.error as ApiException; throw ApiException( message: e.message ?? 'Failed to create billing portal', statusCode: e.response?.statusCode, ); } } /// Get sections for an agent type: GET /profile-sections/agent-type/{agentTypeId} /// Returns { agentType: {...}, sections: [...] } Future> getSectionsForAgentType( String agentTypeId) async { try { final response = await _dio.get('/profile-sections/agent-type/$agentTypeId'); return response.data['data'] as Map; } on DioException catch (e) { if (e.error is ApiException) throw e.error as ApiException; throw ApiException( message: e.message ?? 'Failed to fetch profile sections', statusCode: e.response?.statusCode, ); } } /// Get field values: GET /agents/profile/field-values /// Returns { agentProfileId: "...", fieldValues: [...] } Future> getFieldValues() async { try { final response = await _dio.get('/agents/profile/field-values'); return response.data['data'] as Map; } on DioException catch (e) { if (e.error is ApiException) throw e.error as ApiException; throw ApiException( message: e.message ?? 'Failed to fetch field values', statusCode: e.response?.statusCode, ); } } /// Save field values: PUT /agents/profile/field-values Future saveFieldValues(List> fieldValues) async { try { await _dio.put('/agents/profile/field-values', data: { 'fieldValues': fieldValues, }); } on DioException catch (e) { if (e.error is ApiException) throw e.error as ApiException; throw ApiException( message: e.message ?? 'Failed to save field values', statusCode: e.response?.statusCode, ); } } /// Cancel subscription: POST /stripe/cancel-subscription Future cancelSubscription() async { try { await _dio.post('/stripe/cancel-subscription'); } on DioException catch (e) { if (e.error is ApiException) throw e.error as ApiException; throw ApiException( message: e.message ?? 'Failed to cancel subscription', statusCode: e.response?.statusCode, ); } } /// Get available plans: GET /stripe/plans Future>> getStripePlans() async { try { final response = await _dio.get('/stripe/plans'); final list = response.data['data'] as List; return list.cast>(); } on DioException catch (e) { if (e.error is ApiException) throw e.error as ApiException; throw ApiException( message: e.message ?? 'Failed to fetch plans', statusCode: e.response?.statusCode, ); } } }