/// Registration Page /// /// User registration form with role-based verification requirements. /// Matches design from html/register.html library; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:image_picker/image_picker.dart'; import 'package:worker/core/constants/ui_constants.dart'; import 'package:worker/core/theme/colors.dart'; import 'package:worker/core/utils/validators.dart'; import 'package:worker/features/auth/presentation/widgets/phone_input_field.dart'; import 'package:worker/features/auth/presentation/widgets/file_upload_card.dart'; import 'package:worker/features/auth/presentation/widgets/role_dropdown.dart'; /// Registration Page /// /// Features: /// - Full name, phone, email, password fields /// - Role selection (dealer/worker/broker/other) /// - Conditional verification section for workers/dealers /// - File upload for ID card and certificate /// - Company name and city selection /// - Terms and conditions checkbox /// /// Navigation: /// - From: Login page /// - To: OTP verification (broker/other) or pending approval (worker/dealer) class RegisterPage extends ConsumerStatefulWidget { const RegisterPage({super.key}); @override ConsumerState createState() => _RegisterPageState(); } class _RegisterPageState extends ConsumerState { // Form key final _formKey = GlobalKey(); // Text controllers final _fullNameController = TextEditingController(); final _phoneController = TextEditingController(); final _emailController = TextEditingController(); final _passwordController = TextEditingController(); final _idNumberController = TextEditingController(); final _taxCodeController = TextEditingController(); final _companyController = TextEditingController(); // Focus nodes final _fullNameFocus = FocusNode(); final _phoneFocus = FocusNode(); final _emailFocus = FocusNode(); final _passwordFocus = FocusNode(); final _idNumberFocus = FocusNode(); final _taxCodeFocus = FocusNode(); final _companyFocus = FocusNode(); // State String? _selectedRole; String? _selectedCity; File? _idCardFile; File? _certificateFile; bool _termsAccepted = false; bool _passwordVisible = false; bool _isLoading = false; final _imagePicker = ImagePicker(); @override void dispose() { _fullNameController.dispose(); _phoneController.dispose(); _emailController.dispose(); _passwordController.dispose(); _idNumberController.dispose(); _taxCodeController.dispose(); _companyController.dispose(); _fullNameFocus.dispose(); _phoneFocus.dispose(); _emailFocus.dispose(); _passwordFocus.dispose(); _idNumberFocus.dispose(); _taxCodeFocus.dispose(); _companyFocus.dispose(); super.dispose(); } /// Check if verification section should be shown bool get _shouldShowVerification { return _selectedRole == 'worker' || _selectedRole == 'dealer'; } /// Pick image from gallery or camera Future _pickImage(bool isIdCard) async { try { // Show bottom sheet to select source final source = await showModalBottomSheet( context: context, builder: (context) => SafeArea( child: Wrap( children: [ ListTile( leading: const Icon(Icons.camera_alt), title: const Text('Chụp ảnh'), onTap: () => Navigator.pop(context, ImageSource.camera), ), ListTile( leading: const Icon(Icons.photo_library), title: const Text('Chọn từ thư viện'), onTap: () => Navigator.pop(context, ImageSource.gallery), ), ], ), ), ); if (source == null) return; final pickedFile = await _imagePicker.pickImage( source: source, maxWidth: 1920, maxHeight: 1080, imageQuality: 85, ); if (pickedFile == null) return; final file = File(pickedFile.path); // Validate file size (max 5MB) final fileSize = await file.length(); if (fileSize > 5 * 1024 * 1024) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('File không được vượt quá 5MB'), backgroundColor: AppColors.danger, ), ); } return; } setState(() { if (isIdCard) { _idCardFile = file; } else { _certificateFile = file; } }); } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Lỗi chọn ảnh: $e'), backgroundColor: AppColors.danger, ), ); } } } /// Remove selected image void _removeImage(bool isIdCard) { setState(() { if (isIdCard) { _idCardFile = null; } else { _certificateFile = null; } }); } /// Validate form and submit Future _handleRegister() async { // Validate form if (!_formKey.currentState!.validate()) { return; } // Check terms acceptance if (!_termsAccepted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( 'Vui lòng đồng ý với Điều khoản sử dụng và Chính sách bảo mật', ), backgroundColor: AppColors.warning, ), ); return; } // Validate verification requirements for workers/dealers if (_shouldShowVerification) { if (_idNumberController.text.trim().isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Vui lòng nhập số CCCD/CMND'), backgroundColor: AppColors.warning, ), ); return; } if (_idCardFile == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Vui lòng tải lên ảnh CCCD/CMND'), backgroundColor: AppColors.warning, ), ); return; } if (_certificateFile == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Vui lòng tải lên ảnh chứng chỉ hành nghề hoặc GPKD'), backgroundColor: AppColors.warning, ), ); return; } } setState(() { _isLoading = true; }); try { // TODO: Implement actual registration API call // For now, simulate API delay await Future.delayed(const Duration(seconds: 2)); if (mounted) { // Navigate based on role if (_shouldShowVerification) { // For workers/dealers with verification, show pending page // TODO: Navigate to pending approval page ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( 'Đăng ký thành công! Tài khoản đang chờ xét duyệt.', ), backgroundColor: AppColors.success, ), ); context.pop(); } else { // For other roles, navigate to OTP verification // TODO: Navigate to OTP verification page ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Đăng ký thành công! Vui lòng xác thực OTP.'), backgroundColor: AppColors.success, ), ); // context.push('/otp-verification'); context.pop(); } } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Đăng ký thất bại: $e'), backgroundColor: AppColors.danger, ), ); } } finally { if (mounted) { setState(() { _isLoading = false; }); } } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFFF4F6F8), appBar: AppBar( backgroundColor: AppColors.white, elevation: AppBarSpecs.elevation, leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.black), onPressed: () => context.pop(), ), title: const Text( 'Đăng ký tài khoản', style: TextStyle( color: Colors.black, fontSize: 18, fontWeight: FontWeight.w600, ), ), centerTitle: false, ), body: SafeArea( child: Form( key: _formKey, child: SingleChildScrollView( padding: const EdgeInsets.all(AppSpacing.md), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Welcome section const Text( 'Tạo tài khoản mới', style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: AppColors.grey900, ), textAlign: TextAlign.center, ), const SizedBox(height: AppSpacing.xs), const Text( 'Điền thông tin để bắt đầu', style: TextStyle(fontSize: 14, color: AppColors.grey500), textAlign: TextAlign.center, ), const SizedBox(height: AppSpacing.lg), // Form card Container( decoration: BoxDecoration( color: AppColors.white, borderRadius: BorderRadius.circular(AppRadius.card), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), padding: const EdgeInsets.all(AppSpacing.md), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Full Name _buildLabel('Họ và tên *'), TextFormField( controller: _fullNameController, focusNode: _fullNameFocus, textInputAction: TextInputAction.next, decoration: _buildInputDecoration( hintText: 'Nhập họ và tên', prefixIcon: Icons.person, ), validator: (value) => Validators.minLength( value, 3, fieldName: 'Họ và tên', ), ), const SizedBox(height: AppSpacing.md), // Phone Number _buildLabel('Số điện thoại *'), PhoneInputField( controller: _phoneController, focusNode: _phoneFocus, validator: Validators.phone, ), const SizedBox(height: AppSpacing.md), // Email _buildLabel('Email *'), TextFormField( controller: _emailController, focusNode: _emailFocus, keyboardType: TextInputType.emailAddress, textInputAction: TextInputAction.next, decoration: _buildInputDecoration( hintText: 'Nhập email', prefixIcon: Icons.email, ), validator: Validators.email, ), const SizedBox(height: AppSpacing.md), // Password _buildLabel('Mật khẩu *'), TextFormField( controller: _passwordController, focusNode: _passwordFocus, obscureText: !_passwordVisible, textInputAction: TextInputAction.done, decoration: _buildInputDecoration( hintText: 'Tạo mật khẩu mới', prefixIcon: Icons.lock, suffixIcon: IconButton( icon: Icon( _passwordVisible ? Icons.visibility : Icons.visibility_off, color: AppColors.grey500, ), onPressed: () { setState(() { _passwordVisible = !_passwordVisible; }); }, ), ), validator: (value) => Validators.passwordSimple(value, minLength: 6), ), const SizedBox(height: AppSpacing.xs), const Text( 'Mật khẩu tối thiểu 6 ký tự', style: TextStyle( fontSize: 12, color: AppColors.grey500, ), ), const SizedBox(height: AppSpacing.md), // Role Selection _buildLabel('Vai trò *'), RoleDropdown( value: _selectedRole, onChanged: (value) { setState(() { _selectedRole = value; // Clear verification fields when role changes if (!_shouldShowVerification) { _idNumberController.clear(); _taxCodeController.clear(); _idCardFile = null; _certificateFile = null; } }); }, validator: (value) { if (value == null || value.isEmpty) { return 'Vui lòng chọn vai trò'; } return null; }, ), const SizedBox(height: AppSpacing.md), // Verification Section (conditional) if (_shouldShowVerification) ...[ _buildVerificationSection(), const SizedBox(height: AppSpacing.md), ], // Company Name (optional) _buildLabel('Tên công ty/Cửa hàng'), TextFormField( controller: _companyController, focusNode: _companyFocus, textInputAction: TextInputAction.next, decoration: _buildInputDecoration( hintText: 'Nhập tên công ty (không bắt buộc)', prefixIcon: Icons.business, ), ), const SizedBox(height: AppSpacing.md), // City/Province _buildLabel('Tỉnh/Thành phố *'), DropdownButtonFormField( value: _selectedCity, decoration: _buildInputDecoration( hintText: 'Chọn tỉnh/thành phố', prefixIcon: Icons.location_city, ), items: const [ DropdownMenuItem( value: 'hanoi', child: Text('Hà Nội'), ), DropdownMenuItem( value: 'hcm', child: Text('TP. Hồ Chí Minh'), ), DropdownMenuItem( value: 'danang', child: Text('Đà Nẵng'), ), DropdownMenuItem( value: 'haiphong', child: Text('Hải Phòng'), ), DropdownMenuItem( value: 'cantho', child: Text('Cần Thơ'), ), DropdownMenuItem(value: 'other', child: Text('Khác')), ], onChanged: (value) { setState(() { _selectedCity = value; }); }, validator: (value) { if (value == null || value.isEmpty) { return 'Vui lòng chọn tỉnh/thành phố'; } return null; }, ), const SizedBox(height: AppSpacing.md), // Terms and Conditions Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Checkbox( value: _termsAccepted, onChanged: (value) { setState(() { _termsAccepted = value ?? false; }); }, activeColor: AppColors.primaryBlue, ), Expanded( child: Padding( padding: const EdgeInsets.only(top: 12.0), child: GestureDetector( onTap: () { setState(() { _termsAccepted = !_termsAccepted; }); }, child: const Text.rich( TextSpan( text: 'Tôi đồng ý với ', style: TextStyle(fontSize: 13), children: [ TextSpan( text: 'Điều khoản sử dụng', style: TextStyle( color: AppColors.primaryBlue, fontWeight: FontWeight.w500, ), ), TextSpan(text: ' và '), TextSpan( text: 'Chính sách bảo mật', style: TextStyle( color: AppColors.primaryBlue, fontWeight: FontWeight.w500, ), ), ], ), ), ), ), ), ], ), const SizedBox(height: AppSpacing.lg), // Register Button SizedBox( height: ButtonSpecs.height, child: ElevatedButton( onPressed: _isLoading ? null : _handleRegister, style: ElevatedButton.styleFrom( backgroundColor: AppColors.primaryBlue, foregroundColor: AppColors.white, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( ButtonSpecs.borderRadius, ), ), ), child: _isLoading ? const SizedBox( height: 20, width: 20, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation( AppColors.white, ), ), ) : const Text( 'Đăng ký', style: TextStyle( fontSize: ButtonSpecs.fontSize, fontWeight: ButtonSpecs.fontWeight, ), ), ), ), ], ), ), const SizedBox(height: AppSpacing.lg), // Login Link Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text( 'Đã có tài khoản? ', style: TextStyle(fontSize: 13, color: AppColors.grey500), ), GestureDetector( onTap: () => context.pop(), child: const Text( 'Đăng nhập', style: TextStyle( fontSize: 13, color: AppColors.primaryBlue, fontWeight: FontWeight.w500, ), ), ), ], ), const SizedBox(height: AppSpacing.lg), ], ), ), ), ), ); } /// Build label widget Widget _buildLabel(String text) { return Padding( padding: const EdgeInsets.only(bottom: AppSpacing.xs), child: Text( text, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: AppColors.grey900, ), ), ); } /// Build input decoration InputDecoration _buildInputDecoration({ required String hintText, required IconData prefixIcon, Widget? suffixIcon, }) { return InputDecoration( hintText: hintText, hintStyle: const TextStyle( fontSize: InputFieldSpecs.hintFontSize, color: AppColors.grey500, ), prefixIcon: Icon( prefixIcon, color: AppColors.primaryBlue, size: AppIconSize.md, ), suffixIcon: suffixIcon, filled: true, fillColor: AppColors.white, contentPadding: InputFieldSpecs.contentPadding, border: OutlineInputBorder( borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius), borderSide: const BorderSide(color: AppColors.grey100, width: 1.0), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius), borderSide: const BorderSide(color: AppColors.grey100, width: 1.0), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius), borderSide: const BorderSide(color: AppColors.primaryBlue, width: 2.0), ), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius), borderSide: const BorderSide(color: AppColors.danger, width: 1.0), ), focusedErrorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius), borderSide: const BorderSide(color: AppColors.danger, width: 2.0), ), ); } /// Build verification section Widget _buildVerificationSection() { return Container( decoration: BoxDecoration( color: const Color(0xFFF8FAFC), border: Border.all(color: const Color(0xFFE2E8F0), width: 2), borderRadius: BorderRadius.circular(AppRadius.lg), ), padding: const EdgeInsets.all(AppSpacing.md), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Header Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.shield, color: AppColors.primaryBlue, size: 20), const SizedBox(width: AppSpacing.xs), const Text( 'Thông tin xác thực', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: AppColors.primaryBlue, ), ), ], ), const SizedBox(height: AppSpacing.xs), const Text( 'Thông tin này sẽ được dùng để xác minh tư cách chuyên môn của bạn', style: TextStyle(fontSize: 12, color: AppColors.grey500), textAlign: TextAlign.center, ), const SizedBox(height: AppSpacing.md), // ID Number _buildLabel('Số CCCD/CMND'), TextFormField( controller: _idNumberController, focusNode: _idNumberFocus, keyboardType: TextInputType.number, textInputAction: TextInputAction.next, decoration: _buildInputDecoration( hintText: 'Nhập số CCCD/CMND', prefixIcon: Icons.badge, ), ), const SizedBox(height: AppSpacing.md), // Tax Code _buildLabel('Mã số thuế cá nhân/Công ty'), TextFormField( controller: _taxCodeController, focusNode: _taxCodeFocus, keyboardType: TextInputType.number, textInputAction: TextInputAction.done, decoration: _buildInputDecoration( hintText: 'Nhập mã số thuế (không bắt buộc)', prefixIcon: Icons.receipt_long, ), validator: Validators.taxIdOptional, ), const SizedBox(height: AppSpacing.md), // ID Card Upload _buildLabel('Ảnh mặt trước CCCD/CMND'), FileUploadCard( file: _idCardFile, onTap: () => _pickImage(true), onRemove: () => _removeImage(true), icon: Icons.camera_alt, title: 'Chụp ảnh hoặc chọn file', subtitle: 'JPG, PNG tối đa 5MB', ), const SizedBox(height: AppSpacing.md), // Certificate Upload _buildLabel('Ảnh chứng chỉ hành nghề hoặc GPKD'), FileUploadCard( file: _certificateFile, onTap: () => _pickImage(false), onRemove: () => _removeImage(false), icon: Icons.file_present, title: 'Chụp ảnh hoặc chọn file', subtitle: 'JPG, PNG tối đa 5MB', ), ], ), ); } }