feat: Enhance registration flow with navigation buttons and error handling

This commit is contained in:
vergiLgood1 2025-05-27 01:53:29 +07:00
parent 569e1d6049
commit b6a82438dd
10 changed files with 106 additions and 44 deletions

View File

@ -455,18 +455,19 @@ class AzureOCRService {
if (gender.contains('laki') ||
gender.contains('pria') ||
gender == 'l' ||
gender == 'male') {
extractedInfo['gender'] = 'Male';
gender == 'male' ||
gender == 'm') {
extractedInfo['jenis_kelamin'] = 'LAKI-LAKI';
} else if (gender.contains('perempuan') ||
gender.contains('wanita') ||
gender == 'p' ||
gender == 'female') {
extractedInfo['gender'] = 'Female';
gender == 'female' ||
gender == 'f' ||
gender == 'w') {
extractedInfo['jenis_kelamin'] = 'PEREMPUAN';
} else {
extractedInfo['gender'] = _normalizeCase(genderText);
extractedInfo['jenis_kelamin'] = _normalizeCase(genderText);
// Default to LAKI-LAKI if gender can't be determined
extractedInfo['jenis_kelamin'] = 'LAKI-LAKI';
}
}
@ -1411,7 +1412,7 @@ class AzureOCRService {
// Check if KTP has all required fields
bool isKtpValid(Map<String, String> extractedInfo) {
// Required fields for KTP validation
final requiredFields = ['nik', 'nama', 'jenis_kelamin', 'kewarganegaraan'];
final requiredFields = ['nik', 'nama', 'kewarganegaraan'];
// Check that all required fields are present and not empty
for (var field in requiredFields) {
@ -1452,17 +1453,20 @@ class AzureOCRService {
// }
// // Gender should be either "L" or "P"
final gender = extractedInfo['jenis_kelamin'] ?? '';
if (gender.isEmpty || gender != 'LAKI-LAKI' || gender != 'PEREMPUAN') {
print('KTP validation failed: Missing or invalid gender');
return false;
}
// final gender = extractedInfo['jenis_kelamin'] ?? '';
// if (gender.isEmpty || (gender != 'LAKI-LAKI' && gender != 'PEREMPUAN')) {
// print('KTP validation failed: Missing or invalid gender');
// return false;
// }
// // Nationality should be "WNI"
final nationality = extractedInfo['kewarganegaraan'] ?? 'WNI';
final nationality = extractedInfo['kewarganegaraan'] ?? 'Wbi';
if (nationality.isEmpty || nationality != 'WNI') {
print('KTP validation failed: Missing or invalid');
print('Nationality: $nationality');
if (nationality.isEmpty || nationality != 'Wni') {
print('KTP validation failed: Missing or invalid nationality');
return false;
}

View File

@ -19,6 +19,7 @@ import 'package:sigap/src/features/personalization/data/repositories/profile_rep
import 'package:sigap/src/features/personalization/data/repositories/roles_repository.dart';
import 'package:sigap/src/features/personalization/data/repositories/users_repository.dart';
import 'package:sigap/src/shared/widgets/state_screeen/state_screen.dart';
import 'package:sigap/src/utils/constants/image_strings.dart';
import 'package:sigap/src/utils/constants/num_int.dart';
import 'package:sigap/src/utils/helpers/network_manager.dart';
import 'package:sigap/src/utils/popups/loaders.dart';
@ -74,6 +75,8 @@ class FormRegistrationController extends GetxController {
final RxString submitMessage = RxString('');
final RxBool isSubmitSuccess = RxBool(false);
@override
void onInit() {
super.onInit();
@ -714,6 +717,17 @@ class FormRegistrationController extends GetxController {
try {
isSubmitting.value = true;
// validate all controllers before proceeding
// if (!formKey!.currentState!.validate()) {
// TLoaders.errorSnackBar(
// title: 'Validation Error',
// message: 'Please fill in all required fields correctly.',
// );
// isSubmitting.value = false;
// submitMessage.value = 'Please fill in all required fields correctly.';
// return;
// }
final isConnected = await NetworkManager.instance.isConnected();
if (!isConnected) {
TLoaders.errorSnackBar(
@ -721,6 +735,8 @@ class FormRegistrationController extends GetxController {
message: 'Please check your internet connection and try again.',
);
isSubmitting.value = false;
submitMessage.value =
'Failed to complete registration: No internet connection';
return;
}
@ -744,8 +760,6 @@ class FormRegistrationController extends GetxController {
birthDate: _parseBirthDate(identityController.birthDateController.text),
);
Logger().d('Created user model for update: ${updateUser.toJson()}');
// Check if NIK is already registered (but ignore if it's the current user's NIK)
final isNikTaken = await UserRepository.instance.isNikExists(
updateProfile!.nik!,
@ -757,6 +771,7 @@ class FormRegistrationController extends GetxController {
message: 'NIK already registered to another user!',
);
isSubmitting.value = false;
submitMessage.value = 'NIK already registered to another user!';
return;
}
@ -773,28 +788,27 @@ class FormRegistrationController extends GetxController {
message: 'Failed to update user profile. Please try again.',
);
isSubmitting.value = false;
submitMessage.value =
'Failed to update user profile. Please try again.';
return;
}
// Update user metadata with common profile info
final metadata =
UserMetadataModel(profileStatus: 'completed').toAuthMetadataJson();
// Update metadata first
await userRepository.updateUserMetadata(metadata);
await userRepository.updateProfileStatus('completed');
Logger().d('User metadata updated successfully');
isSubmitSuccess.value = true;
submitMessage.value = 'Registration completed successfully';
// submitMessage.value = 'Registration completed successfully';
Get.off(
() => StateScreen(
title: 'Registration Completed',
subtitle: 'Your registration has been successfully completed.',
icon: Icons.check_circle,
image: TImages.womanHuggingEarth,
isSvg: true,
showButton: true,
primaryButtonTitle: 'Go to Home',
primaryButtonTitle: 'Back to sign in',
onPressed: () => AuthenticationRepository.instance.screenRedirect(),
),
);

View File

@ -546,15 +546,8 @@ class IdentityVerificationController extends GetxController {
// Save registration data using the centralized model
void saveRegistrationData() async {
try {
// Call the appropriate registration update function based on user type
if (isOfficer) {
// For officers, use the officer registration update function
// mainController.updateOfficerRegistrationData();
}
// For regular users, call the three required updates
mainController.updateUserRegistrationData();
} catch (e) {
isDataSaved.value = false;
dataSaveMessage.value = 'Error saving registration data: $e';
@ -564,8 +557,6 @@ class IdentityVerificationController extends GetxController {
}
}
@override
void onClose() {
// Dispose form controllers

View File

@ -48,7 +48,7 @@ class FacialVerificationService {
final detectedFaces = await _edgeFunctionService.detectFaces(image);
return detectedFaces.isNotEmpty;
} catch (e) {
print('Error detecting face: $e');
print('Error detecting face. Please try again later.');
return false;
}
}

View File

@ -280,7 +280,7 @@ class SelfieVerificationController extends GetxController {
await _compareWithIdCard();
} catch (e) {
dev.log('Face detection API error: $e', name: 'SELFIE_VERIFICATION');
selfieError.value = 'Error detecting face: $e';
selfieError.value = 'Error detecting face: Please try again.';
isVerifyingFace.value = false;
isSelfieValid.value = false;
}

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sigap/src/features/auth/presentasion/controllers/signup/main/registration_form_controller.dart';
import 'package:sigap/src/utils/validators/validation.dart';
class PersonalInfoController extends GetxController {
@ -23,6 +24,8 @@ class PersonalInfoController extends GetxController {
final RxString bioError = ''.obs;
final RxString addressError = ''.obs;
final RxString personalInfoError = ''.obs; // General error for personal info
// Manual validation as fallback
final RxBool isFormValid = RxBool(true);
@ -113,6 +116,18 @@ class PersonalInfoController extends GetxController {
return isFormValid.value;
}
void handleNextStep(
GlobalKey<FormState> formKey,
FormRegistrationController mainController,
) {
if (!validate(formKey)) {
personalInfoError.value = 'Please fill the required information.';
return;
}
mainController.nextStep();
}
void clearErrors() {
firstNameError.value = '';
lastNameError.value = '';

View File

@ -97,16 +97,26 @@ class FormRegistrationScreen extends StatelessWidget {
currentStep: controller.currentStep.value,
totalSteps: controller.totalSteps,
stepTitles: controller.getStepTitles(),
onStepTapped: controller.goToStep,
onStepTapped: (index) {
// If stepping forward, use nextStep() to validate
if (index > controller.currentStep.value) {
controller.nextStep();
} else {
// If stepping backward, just go to that step without validation
controller.goToStep(index);
}
},
style: StepIndicatorStyle.standard,
),
),
);
}
// Add navigation buttons
Widget _buildStepContent(FormRegistrationController controller) {
final isOfficer = controller.selectedRole.value?.isOfficer ?? false;
// Different step content for officer vs viewer
if (isOfficer) {
// Officer registration flow (3 steps)

View File

@ -8,6 +8,7 @@ import 'package:sigap/src/features/auth/presentasion/controllers/signup/step/off
import 'package:sigap/src/features/auth/presentasion/controllers/signup/step/selfie-verification/selfie_verification_controller.dart';
import 'package:sigap/src/features/auth/presentasion/controllers/signup/step/viewer-information/personal_info_controller.dart';
import 'package:sigap/src/shared/widgets/form/form_section_header.dart';
import 'package:sigap/src/shared/widgets/navigation/step_navigation_buttons.dart';
import 'package:sigap/src/utils/constants/colors.dart';
import 'package:sigap/src/utils/constants/sizes.dart';
@ -58,7 +59,7 @@ class IdentityVerificationStep extends StatelessWidget {
onPressed:
ctrl.isSavingData.value
? null
: () => _submitRegistrationData(controller, context),
: () => _submitRegistrationData(controller),
style: ElevatedButton.styleFrom(
backgroundColor: TColors.primary,
foregroundColor: Colors.white,
@ -89,6 +90,18 @@ class IdentityVerificationStep extends StatelessWidget {
),
),
Obx(
() => StepNavigationButtons(
showPrevious: mainController.currentStep.value > 0,
isLastStep: false,
onPrevious: mainController.previousStep,
onNext: () => _submitRegistrationData(controller),
isLoading: mainController.isSubmitting.value,
errorMessage: mainController.submitMessage.value,
nextButtonText: 'Continue',
),
),
// Save Result Message
GetBuilder<IdentityVerificationController>(
id: 'saveMessage',
@ -474,10 +487,8 @@ class IdentityVerificationStep extends StatelessWidget {
// Submit registration data
void _submitRegistrationData(
IdentityVerificationController controller,
BuildContext context,
) async {
// Call saveRegistrationData with all the collected data
// Call saveRegistrati`onData with all the collected data
controller.saveRegistrationData();
// The rest of submission logic would be handled in the controller

View File

@ -3,6 +3,7 @@ import 'package:get/get.dart';
import 'package:sigap/src/features/auth/presentasion/controllers/signup/main/registration_form_controller.dart';
import 'package:sigap/src/features/auth/presentasion/controllers/signup/step/viewer-information/personal_info_controller.dart';
import 'package:sigap/src/shared/widgets/form/form_section_header.dart';
import 'package:sigap/src/shared/widgets/navigation/step_navigation_buttons.dart';
import 'package:sigap/src/shared/widgets/text/custom_text_field.dart';
import 'package:sigap/src/utils/constants/sizes.dart';
import 'package:sigap/src/utils/validators/validation.dart';
@ -127,6 +128,20 @@ class PersonalInfoStep extends StatelessWidget {
},
),
),
const SizedBox(height: TSizes.spaceBtwSections),
// Navigation buttons
Obx(
() => StepNavigationButtons(
showPrevious: mainController.currentStep.value > 0,
isLastStep: false,
onPrevious: mainController.previousStep,
onNext: () => controller.handleNextStep(formKey, mainController),
errorMessage: controller.personalInfoError.value,
nextButtonText: 'Continue',
)
),
],
),
);

View File

@ -805,6 +805,8 @@ class SelfieVerificationStep extends StatelessWidget {
SelfieVerificationController controller,
BuildContext context,
) {
final isDark = THelperFunctions.isDarkMode(context);
switch (status) {
case VerificationStatus.initial:
case VerificationStatus.performingLiveness:
@ -817,7 +819,7 @@ class SelfieVerificationStep extends StatelessWidget {
isLoading: true,
title: 'Detecting Face',
icon: Icons.face_retouching_natural,
customColor: TColors.primary,
customColor: isDark ? TColors.accent : TColors.primary,
);
case VerificationStatus.comparingWithID:
@ -827,7 +829,7 @@ class SelfieVerificationStep extends StatelessWidget {
isLoading: true,
title: 'Face Matching',
icon: Icons.compare,
customColor: TColors.primary,
customColor: isDark ? TColors.accent : TColors.primary,
);
case VerificationStatus.livenessCompleted: