2026-03-08 02:01:03 +05:30
|
|
|
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<Map<String, dynamic>> 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<String, dynamic>;
|
|
|
|
|
} 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<Map<String, dynamic>> updateProfile(
|
|
|
|
|
String role,
|
|
|
|
|
Map<String, dynamic> 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<String, dynamic>;
|
|
|
|
|
} 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,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-08 21:48:56 +05:30
|
|
|
/// Get presigned upload URL for avatar.
|
|
|
|
|
/// Returns { uploadUrl: String, key: String }
|
|
|
|
|
Future<Map<String, String>> getAvatarUploadUrl(
|
|
|
|
|
String role, String fileName, String contentType) async {
|
2026-03-08 02:01:03 +05:30
|
|
|
try {
|
|
|
|
|
final endpoint = role == 'AGENT'
|
|
|
|
|
? '/upload/avatar-presigned-url'
|
|
|
|
|
: '/upload/user-avatar-presigned-url';
|
2026-03-08 21:48:56 +05:30
|
|
|
final response = await _dio.post(endpoint, data: {
|
|
|
|
|
'filename': fileName,
|
|
|
|
|
'contentType': contentType,
|
|
|
|
|
});
|
|
|
|
|
final data = response.data['data'] as Map<String, dynamic>;
|
|
|
|
|
return {
|
|
|
|
|
'uploadUrl': data['uploadUrl'] as String,
|
|
|
|
|
'key': data['key'] as String,
|
|
|
|
|
};
|
2026-03-08 02:01:03 +05:30
|
|
|
} 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,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-08 21:48:56 +05:30
|
|
|
|
|
|
|
|
/// Upload file bytes directly to S3 presigned URL.
|
|
|
|
|
Future<void> uploadToS3(
|
|
|
|
|
String uploadUrl, List<int> 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,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Delete avatar from S3: DELETE /upload/{encodedKey}
|
|
|
|
|
Future<void> 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<Map<String, dynamic>> 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<String, dynamic>;
|
|
|
|
|
} 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<Map<String, dynamic>> updatePrivacyPreferences(
|
|
|
|
|
String role,
|
|
|
|
|
Map<String, dynamic> 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<String, dynamic>;
|
|
|
|
|
} 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<void> 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<void> 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<Map<String, dynamic>> 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<String, dynamic>;
|
|
|
|
|
} 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<Map<String, dynamic>> updateNotificationPreferences(
|
|
|
|
|
String role,
|
|
|
|
|
Map<String, dynamic> 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<String, dynamic>;
|
|
|
|
|
} 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<String> 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<List<Map<String, dynamic>>> getMyTestimonials(
|
|
|
|
|
{String sort = 'latest'}) async {
|
|
|
|
|
try {
|
|
|
|
|
final response =
|
|
|
|
|
await _dio.get('/testimonials/my', queryParameters: {'sort': sort});
|
|
|
|
|
final list = response.data['data'] as List<dynamic>;
|
|
|
|
|
return list.cast<Map<String, dynamic>>();
|
|
|
|
|
} 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<Map<String, dynamic>?> getSubscription() async {
|
|
|
|
|
try {
|
|
|
|
|
final response = await _dio.get('/stripe/subscription');
|
|
|
|
|
return response.data['data'] as Map<String, dynamic>?;
|
|
|
|
|
} 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<String> 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<String> 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,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Cancel subscription: POST /stripe/cancel-subscription
|
|
|
|
|
Future<void> 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<List<Map<String, dynamic>>> getStripePlans() async {
|
|
|
|
|
try {
|
|
|
|
|
final response = await _dio.get('/stripe/plans');
|
|
|
|
|
final list = response.data['data'] as List<dynamic>;
|
|
|
|
|
return list.cast<Map<String, dynamic>>();
|
|
|
|
|
} 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,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-08 02:01:03 +05:30
|
|
|
}
|