add auth, format
This commit is contained in:
305
lib/features/auth/presentation/providers/register_provider.dart
Normal file
305
lib/features/auth/presentation/providers/register_provider.dart
Normal file
@@ -0,0 +1,305 @@
|
||||
/// Registration State Provider
|
||||
///
|
||||
/// Manages registration state for the Worker application.
|
||||
/// Handles user registration with role-based validation and verification.
|
||||
///
|
||||
/// Uses Riverpod 3.0 with code generation for type-safe state management.
|
||||
library;
|
||||
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:worker/features/auth/domain/entities/user.dart';
|
||||
|
||||
part 'register_provider.g.dart';
|
||||
|
||||
/// Registration Form Data
|
||||
///
|
||||
/// Contains all data needed for user registration.
|
||||
/// Optional fields are used based on selected role.
|
||||
class RegistrationData {
|
||||
/// Required: Full name of the user
|
||||
final String fullName;
|
||||
|
||||
/// Required: Phone number (Vietnamese format)
|
||||
final String phoneNumber;
|
||||
|
||||
/// Required: Email address
|
||||
final String email;
|
||||
|
||||
/// Required: Password (minimum 6 characters)
|
||||
final String password;
|
||||
|
||||
/// Required: User role
|
||||
final UserRole role;
|
||||
|
||||
/// Optional: CCCD/ID card number (required for dealer/worker roles)
|
||||
final String? cccd;
|
||||
|
||||
/// Optional: Tax code (personal or company)
|
||||
final String? taxCode;
|
||||
|
||||
/// Optional: Company/store name
|
||||
final String? companyName;
|
||||
|
||||
/// Required: Province/city
|
||||
final String? city;
|
||||
|
||||
/// Optional: Attachment file paths (ID card, certificate, license)
|
||||
final List<String>? attachments;
|
||||
|
||||
const RegistrationData({
|
||||
required this.fullName,
|
||||
required this.phoneNumber,
|
||||
required this.email,
|
||||
required this.password,
|
||||
required this.role,
|
||||
this.cccd,
|
||||
this.taxCode,
|
||||
this.companyName,
|
||||
this.city,
|
||||
this.attachments,
|
||||
});
|
||||
|
||||
/// Copy with method for immutability
|
||||
RegistrationData copyWith({
|
||||
String? fullName,
|
||||
String? phoneNumber,
|
||||
String? email,
|
||||
String? password,
|
||||
UserRole? role,
|
||||
String? cccd,
|
||||
String? taxCode,
|
||||
String? companyName,
|
||||
String? city,
|
||||
List<String>? attachments,
|
||||
}) {
|
||||
return RegistrationData(
|
||||
fullName: fullName ?? this.fullName,
|
||||
phoneNumber: phoneNumber ?? this.phoneNumber,
|
||||
email: email ?? this.email,
|
||||
password: password ?? this.password,
|
||||
role: role ?? this.role,
|
||||
cccd: cccd ?? this.cccd,
|
||||
taxCode: taxCode ?? this.taxCode,
|
||||
companyName: companyName ?? this.companyName,
|
||||
city: city ?? this.city,
|
||||
attachments: attachments ?? this.attachments,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Registration State Provider
|
||||
///
|
||||
/// Main provider for user registration state management.
|
||||
/// Handles registration process with role-based validation.
|
||||
///
|
||||
/// Usage in widgets:
|
||||
/// ```dart
|
||||
/// final registerState = ref.watch(registerProvider);
|
||||
/// registerState.when(
|
||||
/// data: (user) => SuccessScreen(user),
|
||||
/// loading: () => LoadingIndicator(),
|
||||
/// error: (error, stack) => ErrorWidget(error),
|
||||
/// );
|
||||
/// ```
|
||||
@riverpod
|
||||
class Register extends _$Register {
|
||||
/// Initialize with no registration result
|
||||
@override
|
||||
Future<User?> build() async {
|
||||
// No initial registration
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Register a new user
|
||||
///
|
||||
/// Performs user registration with role-based validation.
|
||||
/// For dealer/worker roles, requires additional verification documents.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - [data]: Registration form data containing all required fields
|
||||
///
|
||||
/// Returns: Newly created User object on success
|
||||
///
|
||||
/// Throws: Exception on validation failure or registration error
|
||||
///
|
||||
/// Error messages (Vietnamese):
|
||||
/// - "Vui lòng điền đầy đủ thông tin bắt buộc"
|
||||
/// - "Số điện thoại không hợp lệ"
|
||||
/// - "Email không hợp lệ"
|
||||
/// - "Mật khẩu phải có ít nhất 6 ký tự"
|
||||
/// - "Vui lòng nhập số CCCD/CMND" (for dealer/worker)
|
||||
/// - "Vui lòng tải lên ảnh CCCD/CMND" (for dealer/worker)
|
||||
/// - "Vui lòng tải lên ảnh chứng chỉ hành nghề hoặc GPKD" (for dealer/worker)
|
||||
/// - "Số điện thoại đã được đăng ký"
|
||||
/// - "Email đã được đăng ký"
|
||||
Future<void> register(RegistrationData data) async {
|
||||
// Set loading state
|
||||
state = const AsyncValue.loading();
|
||||
|
||||
// Perform registration with error handling
|
||||
state = await AsyncValue.guard(() async {
|
||||
// Validate required fields
|
||||
if (data.fullName.isEmpty ||
|
||||
data.phoneNumber.isEmpty ||
|
||||
data.email.isEmpty ||
|
||||
data.password.isEmpty ||
|
||||
data.city == null ||
|
||||
data.city!.isEmpty) {
|
||||
throw Exception('Vui lòng điền đầy đủ thông tin bắt buộc');
|
||||
}
|
||||
|
||||
// Validate phone number (Vietnamese format: 10 digits starting with 0)
|
||||
final phoneRegex = RegExp(r'^0[0-9]{9}$');
|
||||
if (!phoneRegex.hasMatch(data.phoneNumber)) {
|
||||
throw Exception('Số điện thoại không hợp lệ');
|
||||
}
|
||||
|
||||
// Validate email format
|
||||
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
|
||||
if (!emailRegex.hasMatch(data.email)) {
|
||||
throw Exception('Email không hợp lệ');
|
||||
}
|
||||
|
||||
// Validate password length
|
||||
if (data.password.length < 6) {
|
||||
throw Exception('Mật khẩu phải có ít nhất 6 ký tự');
|
||||
}
|
||||
|
||||
// Role-based validation for dealer/worker (requires verification)
|
||||
if (data.role == UserRole.customer) {
|
||||
// For dealer/worker roles, CCCD and attachments are required
|
||||
if (data.cccd == null || data.cccd!.isEmpty) {
|
||||
throw Exception('Vui lòng nhập số CCCD/CMND');
|
||||
}
|
||||
|
||||
// Validate CCCD format (9 or 12 digits)
|
||||
final cccdRegex = RegExp(r'^[0-9]{9}$|^[0-9]{12}$');
|
||||
if (!cccdRegex.hasMatch(data.cccd!)) {
|
||||
throw Exception('Số CCCD/CMND không hợp lệ (phải có 9 hoặc 12 số)');
|
||||
}
|
||||
|
||||
// Validate attachments
|
||||
if (data.attachments == null || data.attachments!.isEmpty) {
|
||||
throw Exception('Vui lòng tải lên ảnh CCCD/CMND');
|
||||
}
|
||||
|
||||
if (data.attachments!.length < 2) {
|
||||
throw Exception('Vui lòng tải lên ảnh chứng chỉ hành nghề hoặc GPKD');
|
||||
}
|
||||
}
|
||||
|
||||
// Simulate API call delay (2 seconds)
|
||||
await Future<void>.delayed(const Duration(seconds: 2));
|
||||
|
||||
// TODO: In production, call the registration API here
|
||||
// final response = await ref.read(authRepositoryProvider).register(data);
|
||||
|
||||
// Mock: Simulate registration success
|
||||
final now = DateTime.now();
|
||||
|
||||
// Determine initial status based on role
|
||||
// Dealer/Worker require admin approval (pending status)
|
||||
// Other roles are immediately active
|
||||
final initialStatus = data.role == UserRole.customer
|
||||
? UserStatus.pending
|
||||
: UserStatus.active;
|
||||
|
||||
// Create new user entity
|
||||
final newUser = User(
|
||||
userId: 'user_${DateTime.now().millisecondsSinceEpoch}',
|
||||
phoneNumber: data.phoneNumber,
|
||||
fullName: data.fullName,
|
||||
email: data.email,
|
||||
role: data.role,
|
||||
status: initialStatus,
|
||||
loyaltyTier: LoyaltyTier.gold, // Default tier for new users
|
||||
totalPoints: 0, // New users start with 0 points
|
||||
companyInfo: data.companyName != null || data.taxCode != null
|
||||
? CompanyInfo(
|
||||
name: data.companyName,
|
||||
taxId: data.taxCode,
|
||||
businessType: _getBusinessType(data.role),
|
||||
)
|
||||
: null,
|
||||
cccd: data.cccd,
|
||||
attachments: data.attachments ?? [],
|
||||
address: data.city,
|
||||
avatarUrl: null,
|
||||
referralCode: 'REF${data.phoneNumber.substring(0, 6)}',
|
||||
referredBy: null,
|
||||
erpnextCustomerId: null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
lastLoginAt: null, // Not logged in yet
|
||||
);
|
||||
|
||||
return newUser;
|
||||
});
|
||||
}
|
||||
|
||||
/// Reset registration state
|
||||
///
|
||||
/// Clears the registration result. Useful when navigating away
|
||||
/// from success screen or starting a new registration.
|
||||
Future<void> reset() async {
|
||||
state = const AsyncValue.data(null);
|
||||
}
|
||||
|
||||
/// Get business type based on user role
|
||||
String _getBusinessType(UserRole role) {
|
||||
switch (role) {
|
||||
case UserRole.customer:
|
||||
return 'Đại lý/Thầu thợ/Kiến trúc sư';
|
||||
case UserRole.sales:
|
||||
return 'Nhân viên kinh doanh';
|
||||
case UserRole.admin:
|
||||
return 'Quản trị viên';
|
||||
case UserRole.accountant:
|
||||
return 'Kế toán';
|
||||
case UserRole.designer:
|
||||
return 'Thiết kế';
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if registration is in progress
|
||||
bool get isLoading => state.isLoading;
|
||||
|
||||
/// Get registration error if any
|
||||
Object? get error => state.error;
|
||||
|
||||
/// Get registered user if successful
|
||||
User? get registeredUser => state.value;
|
||||
|
||||
/// Check if registration was successful
|
||||
bool get isSuccess => state.hasValue && state.value != null;
|
||||
}
|
||||
|
||||
/// Convenience provider for checking if registration is in progress
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// final isRegistering = ref.watch(isRegisteringProvider);
|
||||
/// if (isRegistering) {
|
||||
/// // Show loading indicator
|
||||
/// }
|
||||
/// ```
|
||||
@riverpod
|
||||
bool isRegistering(Ref ref) {
|
||||
final registerState = ref.watch(registerProvider);
|
||||
return registerState.isLoading;
|
||||
}
|
||||
|
||||
/// Convenience provider for checking if registration was successful
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// final success = ref.watch(registrationSuccessProvider);
|
||||
/// if (success) {
|
||||
/// // Navigate to pending approval or OTP screen
|
||||
/// }
|
||||
/// ```
|
||||
@riverpod
|
||||
bool registrationSuccess(Ref ref) {
|
||||
final registerState = ref.watch(registerProvider);
|
||||
return registerState.hasValue && registerState.value != null;
|
||||
}
|
||||
Reference in New Issue
Block a user