update info
This commit is contained in:
37
docs/user.sh
37
docs/user.sh
@@ -4,3 +4,40 @@ curl --location --request POST 'https://land.dbiz.com//api/method/building_mater
|
||||
--header 'X-Frappe-Csrf-Token: a22fa53eeaa923f71f2fd879d2863a0985a6f2107f5f7f66d34cd62d' \
|
||||
--data ''
|
||||
|
||||
#response user info
|
||||
{
|
||||
"message": {
|
||||
"full_name": "phuoc",
|
||||
"phone": "0978113710",
|
||||
"email": "vodanh.2901@gmail.com",
|
||||
"date_of_birth": null,
|
||||
"gender": null,
|
||||
"avatar": "https://secure.gravatar.com/avatar/753a0e2601b9bd87aed417e2ad123bf8?d=404&s=200",
|
||||
"company_name": "phuoc",
|
||||
"tax_code": null,
|
||||
"id_card_front": null,
|
||||
"id_card_back": null,
|
||||
"certificates": [],
|
||||
"membership_status": "Đã được phê duyệt",
|
||||
"membership_status_color": "Success",
|
||||
"is_verified": true,
|
||||
"credential_display": false
|
||||
}
|
||||
}
|
||||
|
||||
#update user info
|
||||
curl --location 'https://land.dbiz.com//api/method/building_material.building_material.api.user.update_user_info' \
|
||||
--header 'Cookie: sid=a0c9a51c8d1fbbec824283115094bdca939bb829345e0005334aa99f; full_name=phuoc; sid=a0c9a51c8d1fbbec824283115094bdca939bb829345e0005334aa99f; system_user=no; user_id=vodanh.2901%40gmail.com; user_image=https%3A//secure.gravatar.com/avatar/753a0e2601b9bd87aed417e2ad123bf8%3Fd%3D404%26s%3D200' \
|
||||
--header 'X-Frappe-Csrf-Token: a22fa53eeaa923f71f2fd879d2863a0985a6f2107f5f7f66d34cd62d' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"full_name" : "Ha Duy Lam",
|
||||
"date_of_birth" : "2025-12-30",
|
||||
"gender" : "Male",
|
||||
"company_name" : "Ha Duy Lam",
|
||||
"tax_code" : "0912313232",
|
||||
"avatar_base64": null,
|
||||
"id_card_front_base64: null,
|
||||
"id_card_back_base64: null,
|
||||
"certificates_base64": []
|
||||
}'
|
||||
@@ -29,9 +29,11 @@
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>This app needs camera access to scan QR codes</string>
|
||||
<string>Ứng dụng cần quyền truy cập camera để quét mã QR và chụp ảnh giấy tờ xác thực (CCCD/CMND, chứng chỉ hành nghề)</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>This app needs photos access to get QR code from photo library</string>
|
||||
<string>Ứng dụng cần quyền truy cập thư viện ảnh để chọn ảnh giấy tờ xác thực và mã QR từ thiết bị của bạn</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Ứng dụng cần quyền truy cập microphone để ghi âm video nếu cần</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
|
||||
@@ -128,4 +128,95 @@ class UserInfoRemoteDataSource {
|
||||
// Could add cache-busting headers in the future if needed
|
||||
return getUserInfo();
|
||||
}
|
||||
|
||||
/// Update User Info
|
||||
///
|
||||
/// Updates the current user's profile information.
|
||||
///
|
||||
/// API: POST https://land.dbiz.com/api/method/building_material.building_material.api.user.update_user_info
|
||||
///
|
||||
/// Request body:
|
||||
/// ```json
|
||||
/// {
|
||||
/// "full_name": "...",
|
||||
/// "date_of_birth": "YYYY-MM-DD",
|
||||
/// "gender": "Male/Female",
|
||||
/// "company_name": "...",
|
||||
/// "tax_code": "...",
|
||||
/// "avatar_base64": null | base64_string,
|
||||
/// "id_card_front_base64": null | base64_string,
|
||||
/// "id_card_back_base64": null | base64_string,
|
||||
/// "certificates_base64": [] | [base64_string, ...]
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Throws:
|
||||
/// - [UnauthorizedException] if user not authenticated (401)
|
||||
/// - [ServerException] if server error occurs (500+)
|
||||
/// - [NetworkException] for other network errors
|
||||
Future<UserInfoModel> updateUserInfo(Map<String, dynamic> data) async {
|
||||
try {
|
||||
debugPrint('🔵 [UserInfoDataSource] Updating user info...');
|
||||
debugPrint('🔵 [UserInfoDataSource] Data: $data');
|
||||
final startTime = DateTime.now();
|
||||
|
||||
// Make POST request with update data
|
||||
final response = await _dioClient.post<Map<String, dynamic>>(
|
||||
'/api/method/building_material.building_material.api.user.update_user_info',
|
||||
data: data,
|
||||
);
|
||||
|
||||
final duration = DateTime.now().difference(startTime);
|
||||
debugPrint('🟢 [UserInfoDataSource] Update response received in ${duration.inMilliseconds}ms');
|
||||
debugPrint('🟢 [UserInfoDataSource] Status: ${response.statusCode}');
|
||||
|
||||
// Check response status
|
||||
if (response.statusCode == 200) {
|
||||
// After successful update, fetch fresh user info
|
||||
debugPrint('✅ [UserInfoDataSource] Successfully updated user info');
|
||||
return await getUserInfo();
|
||||
} else {
|
||||
throw ServerException(
|
||||
'Failed to update user info: ${response.statusCode}',
|
||||
response.statusCode,
|
||||
);
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
// Handle specific HTTP status codes
|
||||
if (e.response?.statusCode == 401) {
|
||||
throw const UnauthorizedException(
|
||||
'Session expired. Please login again.',
|
||||
);
|
||||
} else if (e.response?.statusCode == 403) {
|
||||
throw const ForbiddenException();
|
||||
} else if (e.response?.statusCode == 404) {
|
||||
throw NotFoundException('Update user info endpoint not found');
|
||||
} else if (e.response?.statusCode != null &&
|
||||
e.response!.statusCode! >= 500) {
|
||||
throw ServerException(
|
||||
'Server error: ${e.response?.statusMessage ?? "Unknown error"}',
|
||||
e.response?.statusCode,
|
||||
);
|
||||
} else if (e.type == DioExceptionType.connectionTimeout ||
|
||||
e.type == DioExceptionType.receiveTimeout) {
|
||||
throw const TimeoutException();
|
||||
} else if (e.type == DioExceptionType.connectionError) {
|
||||
throw const NoInternetException();
|
||||
} else {
|
||||
throw NetworkException(
|
||||
e.message ?? 'Failed to update user info',
|
||||
statusCode: e.response?.statusCode,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
// Handle unexpected errors
|
||||
if (e is ServerException ||
|
||||
e is UnauthorizedException ||
|
||||
e is NetworkException ||
|
||||
e is NotFoundException) {
|
||||
rethrow;
|
||||
}
|
||||
throw ServerException('Unexpected error: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,15 @@ class UserInfoModel {
|
||||
this.taxId,
|
||||
this.address,
|
||||
this.cccd,
|
||||
this.dateOfBirth,
|
||||
this.gender,
|
||||
this.idCardFront,
|
||||
this.idCardBack,
|
||||
this.certificates = const [],
|
||||
this.membershipStatus,
|
||||
this.membershipStatusColor,
|
||||
this.isVerified = false,
|
||||
this.credentialDisplay = false,
|
||||
this.referralCode,
|
||||
this.erpnextCustomerId,
|
||||
this.createdAt,
|
||||
@@ -48,6 +57,15 @@ class UserInfoModel {
|
||||
final String? taxId;
|
||||
final String? address;
|
||||
final String? cccd;
|
||||
final DateTime? dateOfBirth;
|
||||
final String? gender;
|
||||
final String? idCardFront;
|
||||
final String? idCardBack;
|
||||
final List<String> certificates;
|
||||
final String? membershipStatus;
|
||||
final String? membershipStatusColor;
|
||||
final bool isVerified;
|
||||
final bool credentialDisplay;
|
||||
final String? referralCode;
|
||||
final String? erpnextCustomerId;
|
||||
final DateTime? createdAt;
|
||||
@@ -87,9 +105,9 @@ class UserInfoModel {
|
||||
/// }
|
||||
/// ```
|
||||
factory UserInfoModel.fromJson(Map<String, dynamic> json) {
|
||||
// API response structure: { "message": { "success": true, "data": {...} } }
|
||||
final message = json['message'] as Map<String, dynamic>?;
|
||||
final data = message?['data'] as Map<String, dynamic>? ?? json;
|
||||
// API response structure: { "message": { "full_name": "...", ... } }
|
||||
// Data is directly under 'message', not nested in 'data'
|
||||
final data = json['message'] as Map<String, dynamic>? ?? json;
|
||||
|
||||
return UserInfoModel(
|
||||
// Use email as userId since API doesn't provide user_id
|
||||
@@ -108,11 +126,22 @@ class UserInfoModel {
|
||||
companyName: data['company_name'] as String?,
|
||||
taxId: data['tax_code'] as String?,
|
||||
address: data['address'] as String?,
|
||||
cccd: data['id_card_front'] as String?, // Store front ID card
|
||||
cccd: null, // CCCD number not in API
|
||||
dateOfBirth: _parseDateTime(data['date_of_birth'] as String?),
|
||||
gender: data['gender'] as String?,
|
||||
idCardFront: data['id_card_front'] as String?,
|
||||
idCardBack: data['id_card_back'] as String?,
|
||||
certificates: (data['certificates'] as List<dynamic>?)
|
||||
?.map((e) => e.toString())
|
||||
.toList() ?? const [],
|
||||
membershipStatus: data['membership_status'] as String?,
|
||||
membershipStatusColor: data['membership_status_color'] as String?,
|
||||
isVerified: data['is_verified'] as bool? ?? false,
|
||||
credentialDisplay: data['credential_display'] as bool? ?? false,
|
||||
referralCode: null,
|
||||
erpnextCustomerId: null,
|
||||
createdAt: _parseDateTime(data['date_of_birth'] as String?),
|
||||
updatedAt: _parseDateTime(data['date_of_birth'] as String?),
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -164,6 +193,15 @@ class UserInfoModel {
|
||||
taxId: taxId,
|
||||
address: address,
|
||||
cccd: cccd,
|
||||
dateOfBirth: dateOfBirth,
|
||||
gender: gender,
|
||||
idCardFront: idCardFront,
|
||||
idCardBack: idCardBack,
|
||||
certificates: certificates,
|
||||
membershipStatus: membershipStatus,
|
||||
membershipStatusColor: membershipStatusColor,
|
||||
isVerified: isVerified,
|
||||
credentialDisplay: credentialDisplay,
|
||||
referralCode: referralCode,
|
||||
erpnextCustomerId: erpnextCustomerId,
|
||||
createdAt: createdAt ?? now,
|
||||
|
||||
@@ -88,6 +88,41 @@ class UserInfoRepositoryImpl implements UserInfoRepository {
|
||||
throw ServerException('Failed to refresh user info: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// UPDATE USER INFO
|
||||
// =========================================================================
|
||||
|
||||
@override
|
||||
Future<UserInfo> updateUserInfo(Map<String, dynamic> data) async {
|
||||
try {
|
||||
_debugPrint('Updating user info via API');
|
||||
_debugPrint('Update data: $data');
|
||||
|
||||
// Update via remote datasource (will fetch fresh data after update)
|
||||
final userInfoModel = await remoteDataSource.updateUserInfo(data);
|
||||
|
||||
_debugPrint('Successfully updated user info: ${userInfoModel.fullName}');
|
||||
|
||||
// Convert model to entity
|
||||
return userInfoModel.toEntity();
|
||||
} on UnauthorizedException catch (e) {
|
||||
_debugPrint('Unauthorized error on update: $e');
|
||||
rethrow;
|
||||
} on NotFoundException catch (e) {
|
||||
_debugPrint('Not found error on update: $e');
|
||||
rethrow;
|
||||
} on ServerException catch (e) {
|
||||
_debugPrint('Server error on update: $e');
|
||||
rethrow;
|
||||
} on NetworkException catch (e) {
|
||||
_debugPrint('Network error on update: $e');
|
||||
rethrow;
|
||||
} catch (e) {
|
||||
_debugPrint('Unexpected error on update: $e');
|
||||
throw ServerException('Failed to update user info: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -60,6 +60,33 @@ class UserInfo {
|
||||
/// CCCD/ID card number
|
||||
final String? cccd;
|
||||
|
||||
/// Date of birth
|
||||
final DateTime? dateOfBirth;
|
||||
|
||||
/// Gender
|
||||
final String? gender;
|
||||
|
||||
/// ID card front image URL
|
||||
final String? idCardFront;
|
||||
|
||||
/// ID card back image URL
|
||||
final String? idCardBack;
|
||||
|
||||
/// Certificate image URLs
|
||||
final List<String> certificates;
|
||||
|
||||
/// Membership verification status text
|
||||
final String? membershipStatus;
|
||||
|
||||
/// Membership status color indicator
|
||||
final String? membershipStatusColor;
|
||||
|
||||
/// Whether user is verified
|
||||
final bool isVerified;
|
||||
|
||||
/// Whether to display credential verification form
|
||||
final bool credentialDisplay;
|
||||
|
||||
/// Referral code
|
||||
final String? referralCode;
|
||||
|
||||
@@ -88,6 +115,15 @@ class UserInfo {
|
||||
this.taxId,
|
||||
this.address,
|
||||
this.cccd,
|
||||
this.dateOfBirth,
|
||||
this.gender,
|
||||
this.idCardFront,
|
||||
this.idCardBack,
|
||||
this.certificates = const [],
|
||||
this.membershipStatus,
|
||||
this.membershipStatusColor,
|
||||
this.isVerified = false,
|
||||
this.credentialDisplay = false,
|
||||
this.referralCode,
|
||||
this.erpnextCustomerId,
|
||||
required this.createdAt,
|
||||
@@ -151,6 +187,15 @@ class UserInfo {
|
||||
String? taxId,
|
||||
String? address,
|
||||
String? cccd,
|
||||
DateTime? dateOfBirth,
|
||||
String? gender,
|
||||
String? idCardFront,
|
||||
String? idCardBack,
|
||||
List<String>? certificates,
|
||||
String? membershipStatus,
|
||||
String? membershipStatusColor,
|
||||
bool? isVerified,
|
||||
bool? credentialDisplay,
|
||||
String? referralCode,
|
||||
String? erpnextCustomerId,
|
||||
DateTime? createdAt,
|
||||
@@ -172,6 +217,15 @@ class UserInfo {
|
||||
taxId: taxId ?? this.taxId,
|
||||
address: address ?? this.address,
|
||||
cccd: cccd ?? this.cccd,
|
||||
dateOfBirth: dateOfBirth ?? this.dateOfBirth,
|
||||
gender: gender ?? this.gender,
|
||||
idCardFront: idCardFront ?? this.idCardFront,
|
||||
idCardBack: idCardBack ?? this.idCardBack,
|
||||
certificates: certificates ?? this.certificates,
|
||||
membershipStatus: membershipStatus ?? this.membershipStatus,
|
||||
membershipStatusColor: membershipStatusColor ?? this.membershipStatusColor,
|
||||
isVerified: isVerified ?? this.isVerified,
|
||||
credentialDisplay: credentialDisplay ?? this.credentialDisplay,
|
||||
referralCode: referralCode ?? this.referralCode,
|
||||
erpnextCustomerId: erpnextCustomerId ?? this.erpnextCustomerId,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
|
||||
@@ -32,4 +32,27 @@ abstract class UserInfoRepository {
|
||||
///
|
||||
/// Returns [UserInfo] entity with fresh user data.
|
||||
Future<UserInfo> refreshUserInfo();
|
||||
|
||||
/// Update user information
|
||||
///
|
||||
/// Updates the authenticated user's profile information.
|
||||
///
|
||||
/// [data] should contain:
|
||||
/// - full_name: String
|
||||
/// - date_of_birth: String (YYYY-MM-DD format)
|
||||
/// - gender: String
|
||||
/// - company_name: String?
|
||||
/// - tax_code: String?
|
||||
/// - avatar_base64: String? (base64 encoded image)
|
||||
/// - id_card_front_base64: String? (base64 encoded image)
|
||||
/// - id_card_back_base64: String? (base64 encoded image)
|
||||
/// - certificates_base64: List<String> (array of base64 encoded images)
|
||||
///
|
||||
/// Returns updated [UserInfo] entity after successful update.
|
||||
///
|
||||
/// Throws:
|
||||
/// - [UnauthorizedException] if session expired
|
||||
/// - [NetworkException] if network error occurs
|
||||
/// - [ServerException] if server error occurs
|
||||
Future<UserInfo> updateUserInfo(Map<String, dynamic> data);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -103,6 +103,36 @@ class UserInfo extends _$UserInfo {
|
||||
});
|
||||
}
|
||||
|
||||
/// Update user information
|
||||
///
|
||||
/// Sends updated user data to the API and refreshes the state.
|
||||
///
|
||||
/// [data] should contain:
|
||||
/// - full_name: String
|
||||
/// - date_of_birth: String (YYYY-MM-DD)
|
||||
/// - gender: String
|
||||
/// - company_name: String?
|
||||
/// - tax_code: String?
|
||||
/// - avatar_base64: String? (base64 encoded)
|
||||
/// - id_card_front_base64: String? (base64 encoded)
|
||||
/// - id_card_back_base64: String? (base64 encoded)
|
||||
/// - certificates_base64: List<String> (base64 encoded)
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// await ref.read(userInfoProvider.notifier).updateUserInfo(updateData);
|
||||
/// ```
|
||||
Future<void> updateUserInfo(Map<String, dynamic> data) async {
|
||||
// Set loading state
|
||||
state = const AsyncValue.loading();
|
||||
|
||||
// Update via repository and fetch fresh data
|
||||
state = await AsyncValue.guard(() async {
|
||||
final repository = await ref.read(userInfoRepositoryProvider.future);
|
||||
return await repository.updateUserInfo(data);
|
||||
});
|
||||
}
|
||||
|
||||
/// Update user info locally
|
||||
///
|
||||
/// Updates the cached state without fetching from API.
|
||||
|
||||
@@ -232,7 +232,7 @@ final class UserInfoProvider
|
||||
UserInfo create() => UserInfo();
|
||||
}
|
||||
|
||||
String _$userInfoHash() => r'74fe20082e7acbb23f9606bd01fdf43fd4c5a893';
|
||||
String _$userInfoHash() => r'ed28fdf0213dfd616592b9735cd291f147867047';
|
||||
|
||||
/// User Info Provider
|
||||
///
|
||||
|
||||
@@ -711,6 +711,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
image:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: image
|
||||
sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.4"
|
||||
image_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@@ -70,6 +70,7 @@ dependencies:
|
||||
intl: ^0.20.0
|
||||
share_plus: ^12.0.1
|
||||
image_picker: ^1.1.2
|
||||
image: ^4.5.4
|
||||
file_picker: ^8.0.0
|
||||
url_launcher: ^6.3.0
|
||||
path_provider: ^2.1.3
|
||||
|
||||
Reference in New Issue
Block a user