diff --git a/.env.development b/.env.development index e1b2169..9bcc9dc 100755 --- a/.env.development +++ b/.env.development @@ -1,4 +1,4 @@ -API_URL=https://venbait.in/enrollment/employeeRest/ -API_URL_POST=https://venbait.in/nhance/dev/employeeRest/ -BASE_HREF=/nhance/hr/dev/ -ENV=development \ No newline at end of file +API_URL=https://app.nhanceindia.in/enrolment/employeeRest/ +API_URL_POST=https://app.nhanceindia.in/zenith/employeeRest/ +BASE_HREF=/hr/ +ENV=production \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index c3e63b3..282fcfb 100755 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -45,7 +45,7 @@ android { applicationId "com.example.nhance_app_pwa" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. - minSdkVersion 23 + minSdkVersion flutter.minSdkVersion targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/lib/branch/branch_card_widget.dart b/lib/branch/branch_card_widget.dart new file mode 100644 index 0000000..d2fd919 --- /dev/null +++ b/lib/branch/branch_card_widget.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +class BranchCard extends StatelessWidget { + final String clientName; + final String branchName; + final bool isSelected; + final VoidCallback onTap; + + const BranchCard({ + Key? key, + required this.clientName, + required this.branchName, + required this.isSelected, + required this.onTap, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(8), + child: Container( + decoration: BoxDecoration( + color: isSelected ? Color(0xFF00999E) : Color(0xFFF0F9F9), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Color(0xFF00999E), + width: 1.5, + ), + ), + padding: EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + clientName, + style: GoogleFonts.poppins( + fontSize: 16, + fontWeight: FontWeight.w500, + color: isSelected ? Colors.white : Color(0xFF00999E), + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + SizedBox(height: 4), + Text( + branchName, + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w400, + color: isSelected ? Color(0xFFFDFDFD) : Color(0xFF000000), + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/branch/branch_selection_page.dart b/lib/branch/branch_selection_page.dart new file mode 100644 index 0000000..cfc1529 --- /dev/null +++ b/lib/branch/branch_selection_page.dart @@ -0,0 +1,272 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import '../customAppBar/customAppBar.dart'; +import '../customAppBar/customFooter.dart'; +import '../customAppBar/toastHelper.dart'; +import '../service/token_storage_service.dart'; +import 'branch_card_widget.dart'; + + +class BranchSelectionPage extends StatefulWidget { + const BranchSelectionPage({Key? key}) : super(key: key); + + @override + State createState() => _BranchSelectionPageState(); +} + +class _BranchSelectionPageState extends State { + List> branches = []; + int? selectedIndex; + final tokenStorage = TokenStorageService(); + + @override + void initState() { + super.initState(); + _loadBranches(); + } + + void _loadBranches() { + setState(() { + branches = tokenStorage.getCombinedBranches(); + }); + } + + void _selectBranch(int index) { + setState(() { + selectedIndex = index; + }); + } + + Future _handleNext() async { + if (selectedIndex == null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Please select a branch'), + backgroundColor: Colors.orange, + ), + ); + return; + } + + final selectedBranch = branches[selectedIndex!]; + + // Save selected branch and decode token + await tokenStorage.saveSelectedBranch(selectedBranch); + + // Debug: Print decoded token + final decodedToken = tokenStorage.getDecodedToken(); + print('Selected Branch: $selectedBranch'); + print('Decoded Token: $decodedToken'); + + // Navigate to home + if (mounted) { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + + //Post Token Decode Details + final empClientBranchId = decodedToken?['post_branch_id']; + await prefs.setString('empClientBranchId', empClientBranchId); + + final empPrimaryId = decodedToken?['post_hr_id']; + await prefs.setString('empPrimaryId', empPrimaryId); + + final empClientId = decodedToken?['post_client_id']; + await prefs.setString('empClientId', empClientId); + + final empHrId = decodedToken?['post_hr_id']; + await prefs.setString('empHrId', empHrId); + + dynamic allowedModules = decodedToken?['allowed_modules']; + +// If it's a String, decode it again + if (allowedModules is String) { + allowedModules = jsonDecode(allowedModules); + } + + final empAllowedModules = allowedModules['post']; + + await prefs.setString('empAllowed_modules', jsonEncode(empAllowedModules)); + + // final empAllowed_modules = decodedToken?['allowed_modules'][0]['post']; + // await prefs.setString('empAllowed_modules', jsonEncode(empAllowed_modules)); + + + //Pre Token Decode Details + final enrollmentEmpClientBranchId = decodedToken?['pre_branch_id']; + await prefs.setString('enrollmentEmpClientBranchId', enrollmentEmpClientBranchId); + + final enrollmentEmpPrimaryId = decodedToken?['pre_hr_id']; + await prefs.setString('enrollmentEmpPrimaryId', enrollmentEmpPrimaryId); + + final enrollmentClient_id = decodedToken?['pre_client_id']; + await prefs.setString('enrollmentClient_id', enrollmentClient_id); + + final enrollmentHrId = decodedToken?['pre_hr_id']; + await prefs.setString('enrollmentHrId', enrollmentHrId); + + + final enrollmentAllowedModules = allowedModules['pre']; + + await prefs.setString('enrollmentAllowed_modules', jsonEncode(enrollmentAllowedModules)); + + // final enrollmentAllowed_modules = decodedToken?['allowed_modules'][1]['pre']; + // await prefs.setString('enrollmentAllowed_modules', jsonEncode(enrollmentAllowed_modules)); + + final token = await tokenStorage.getCurrentToken(); + print('tokenbranch - $token'); + await prefs.setString('token', token!); + ToastHelper.showSuccessToast(context, 'Successfully Login'); + Navigator.pushReplacementNamed(context, 'hrDashboard'); + + } + } + + @override + Widget build(BuildContext context) { + final screenHeight = MediaQuery.of(context).size.height; + final screenWidth = MediaQuery.of(context).size.width; + + // Calculate dynamic horizontal padding (10% of screen width) + final horizontalPadding = screenWidth * 0.1; + + // Calculate dynamic grid height + double gridHeight = screenHeight * 0.45; + if (gridHeight < 300) gridHeight = 300; + if (gridHeight > 600) gridHeight = 600; + + // Determine crossAxisCount with multiple breakpoints + int crossAxisCount; + double childAspectRatio; + + if (screenWidth < 600) { + // Mobile phones + crossAxisCount = 2; + childAspectRatio = 2.5; + } else if (screenWidth < 900) { + // Small tablets + crossAxisCount = 2; + childAspectRatio = 2.8; + } else if (screenWidth < 1200) { + // Large tablets + crossAxisCount = 3; + childAspectRatio = 3; + } else { + // Desktop + crossAxisCount = 3; + childAspectRatio = 5; + } + return Scaffold( + backgroundColor: Color(0xFFEFF3F6), + appBar: CustomAppBar(), + body: Column( + children: [ + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: horizontalPadding , // Inner horizontal padding + vertical: 20, // Inner vertical padding + ), // Outer padding + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 10, + offset: Offset(0, 4), + ), + ], + ), + padding: const EdgeInsets.symmetric( + horizontal: 24.0, // Inner horizontal padding + vertical: 28.0, // Inner vertical padding + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Select Client', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ), + SizedBox(height: 25), + + Container( + height: gridHeight, // Shows approximately 3 rows + child: branches.isEmpty + ? Center( + child: Text( + 'No branches available', + style: TextStyle(fontSize: 16, color: Colors.grey), + ), + ) + : GridView.builder( + // Enable scrolling if content exceeds height + physics: ClampingScrollPhysics(), + padding: EdgeInsets.zero, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: crossAxisCount, + childAspectRatio: childAspectRatio, + crossAxisSpacing: 15, + mainAxisSpacing: 15, + ), + itemCount: branches.length, + itemBuilder: (context, index) { + final branch = branches[index]; + return BranchCard( + clientName: branch['client_name'] ?? 'Unknown Client', + branchName: branch['branch_name'] ?? 'Unknown Branch', + isSelected: selectedIndex == index, + onTap: () => _selectBranch(index), + ); + }, + ), + ), + + SizedBox(height: 24), + + Center( + child: SizedBox( + width: 120, + height: 48, + child: ElevatedButton( + onPressed: _handleNext, + style: ElevatedButton.styleFrom( + backgroundColor: Color(0xFFFF6B35), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + elevation: 0, + ), + child: Text( + 'Next', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + ), + ), + ), + ], + ), + ), + ), + ), + ), + Container( + width: double.infinity, + child: CustomFooter(), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/customAppBar/customAppBar.dart b/lib/customAppBar/customAppBar.dart index 94b795e..83a6bb6 100755 --- a/lib/customAppBar/customAppBar.dart +++ b/lib/customAppBar/customAppBar.dart @@ -87,12 +87,29 @@ class _CustomAppBarState extends State { // }, // ), // if (showBackToHR) - // NavBarItem( - // text: "Back To HR", - // onTap: () { - // // hrLogout(context); - // }, - // ), + NavBarItem( + text: "Change Branch", + onTap: () async { + final prefs = await SharedPreferences.getInstance(); + await prefs.remove('selected_branch'); + await prefs.remove('decoded_token'); + await prefs.remove('clientLogo'); + await prefs.remove('clientName'); + await prefs.remove('empAllowed_modules'); + await prefs.remove('empClientBranchId'); + await prefs.remove('empClientId'); + await prefs.remove('empEmail'); + await prefs.remove('empHrId'); + await prefs.remove('empPrimaryId'); + await prefs.remove('enrollmentAllowed_modules'); + await prefs.remove('enrollmentClient_id'); + await prefs.remove('enrollmentEmpClientBranchId'); + await prefs.remove('enrollmentEmpPrimaryId'); + await prefs.remove('enrollmentHrId'); + await prefs.remove('token'); + Navigator.pushNamed(context, 'branchSelection'); + }, + ), NavBarItem( text: "Logout", onTap: () async { diff --git a/lib/email_verify.dart b/lib/email_verify.dart index 61353d4..798c65a 100644 --- a/lib/email_verify.dart +++ b/lib/email_verify.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:nhancepolicy/responsive.dart'; import 'package:nhancepolicy/service/api_service.dart'; +import 'package:nhancepolicy/service/token_storage_service.dart'; import 'package:pinput/pinput.dart'; import 'dart:async'; import 'package:flutter/gestures.dart'; @@ -17,16 +18,15 @@ import '../customAppBar/toastHelper.dart'; import '../models/environment.dart'; import '../models/platform_helper_mobile.dart' if (dart.library.html) '../models/platform_helper_other.dart'; +import 'branch/branch_selection_page.dart'; // import 'dart:html' as html; class MyEmailVerify extends StatefulWidget { - final String email; + final String type; // 'email' or 'mobile' + final String value; - const MyEmailVerify({ - Key? key, - required this.email, - }) : super(key: key); + const MyEmailVerify({super.key, required this.type, required this.value}); @override State createState() => _MyEmailVerifyState(); @@ -99,9 +99,12 @@ class _MyEmailVerifyState extends State { void verifyOTP(String otp) async { try { + final Map payload = (widget.type == 'mobile') + ? {'otp': _otpController.text,'mobile_no': widget.value} + : {'otp': _otpController.text, 'email': widget.value}; final response = await http.post( Uri.parse(Environment.apiUrl + 'getVerifiedHrData'), - body: json.encode({'email': widget.email, 'otp': _otpController.text}), + body: json.encode(payload), headers: { HttpHeaders.contentTypeHeader: 'application/json', }, @@ -112,35 +115,56 @@ class _MyEmailVerifyState extends State { _isLoading = false; }); Map data = json.decode(response.body); - print('data: $data'); - _token = data['data']; - String status = data['status']; + print('Response data: $data'); - // Directly access the post_enrollment data - Map post = data['post_enrollment']; - print('post: $post'); + // Extract enrollment data + List preEnrollmentData = data['data'] ?? []; + Map postEnrollment = data['post_enrollment'] ?? {}; + List postEnrollmentData = postEnrollment['data'] ?? []; - _postToken = post['data']; - String postStatus = post['status']; + // Save to global storage + final tokenStorage = TokenStorageService(); + await tokenStorage.saveEnrollmentData( + preEnrollmentData, + postEnrollmentData, + ); - if (postStatus == 'success') { - setState(() { - _isLoading = false; - }); - postSuccessData(post, data); - } else if (status == 'success') { - setState(() { - _isLoading = false; - }); - enrollmentSuccessData(data); - } else { - setState(() { - _isLoading = false; - }); - ToastHelper.showErrorToast(context, 'Invalid OTP. Please try again'); - // Show a Snackbar if the OTP is invalid - print('Invalid OTP. Please try again'); - } + // Navigate to branch selection + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => BranchSelectionPage(), + ), + ); + // print('data: $data'); + // _token = data['data']; + // String status = data['status']; + // + // // Directly access the post_enrollment data + // Map post = data['post_enrollment']; + // print('post: $post'); + // + // _postToken = post['data']; + // String postStatus = post['status']; + // + // if (postStatus == 'success') { + // setState(() { + // _isLoading = false; + // }); + // postSuccessData(post, data); + // } else if (status == 'success') { + // setState(() { + // _isLoading = false; + // }); + // enrollmentSuccessData(data); + // } else { + // setState(() { + // _isLoading = false; + // }); + // ToastHelper.showErrorToast(context, 'Invalid OTP. Please try again'); + // // Show a Snackbar if the OTP is invalid + // print('Invalid OTP. Please try again'); + // } } else { setState(() { _isLoading = false; @@ -289,9 +313,17 @@ class _MyEmailVerifyState extends State { Future verifyMobileAndEmailNumber() async { try { // var enteredMobileNumber = mobileController.text; + final Map payload = (widget.type == 'mobile') + ? {'mobile_number': widget.value} + : {'email': widget.value}; + + String apiEndpoint = (widget.type == 'mobile') + ? Environment.apiUrl + 'verifyHrWithMobileNumber' + : Environment.apiUrl + 'verifyHrWithEmail'; + final response = await http.post( - Uri.parse(Environment.apiUrl + 'verifyEmployeeEmailId'), - body: json.encode({'email': widget.email}), + Uri.parse(apiEndpoint), + body: json.encode(payload), headers: { HttpHeaders.contentTypeHeader: 'application/json', }, @@ -303,7 +335,7 @@ class _MyEmailVerifyState extends State { String message = data['data']['message']; if (userVerification) { ToastHelper.showSuccessToast( - context, 'Verification code sent to ${widget.email}'); + context, 'Verification code sent to ${widget.value}'); } else { ToastHelper.showErrorToast(context, message); print('Invalid mobile number'); @@ -622,14 +654,17 @@ class _MyEmailVerifyState extends State { textAlign: TextAlign.center, text: TextSpan( text: - "Please enter the one-time usage code sent to your mobile number ${widget.email}", + "Please enter the one-time usage code sent to your ", style: TextStyle( fontSize: 12, height: 1.5, color: Color(0xFF000000)), children: [ TextSpan( - text: " (Change Email)", + text: widget.type == 'mobile' ? 'mobile number ${widget.value}' : 'Email Id ${widget.value}', + ), + TextSpan( + text: widget.type == 'mobile' ? ' (Change Mobile)' : ' (Change Email)', style: TextStyle( fontSize: 12, color: Color( diff --git a/lib/excel_verification.dart b/lib/excel_verification.dart index f0e1883..77dde9a 100755 --- a/lib/excel_verification.dart +++ b/lib/excel_verification.dart @@ -495,6 +495,9 @@ class _excelVerifyState extends State { }); handleImportAction(); } else { + setState(() { + isLoading = false; + }); ToastHelper.showErrorToast(context, data['message']); print('Table'); diff --git a/lib/hrDashboard.dart b/lib/hrDashboard.dart index 54c9f6f..a0a84b1 100755 --- a/lib/hrDashboard.dart +++ b/lib/hrDashboard.dart @@ -12,6 +12,7 @@ import 'package:nhancepolicy/service/hrDashboardTabs/activePolicies.dart'; import 'package:nhancepolicy/service/hrDashboardTabs/cd.dart'; import 'package:nhancepolicy/service/hrDashboardTabs/claims.dart'; import 'package:nhancepolicy/service/hrDashboardTabs/preEnrollment.dart'; +import 'package:nhancepolicy/service/token_storage_service.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:http/http.dart' as http; import 'customAppBar/customAppBar.dart'; @@ -75,6 +76,7 @@ class _hrDashboardState extends State ]; ScrollController _scrollController = ScrollController(); int selectedIndex = 0; + final tokenService = TokenStorageService(); @override void initState() { @@ -94,6 +96,7 @@ class _hrDashboardState extends State // isLoading = false; // }); // }); + } @override @@ -104,11 +107,15 @@ class _hrDashboardState extends State checkToken() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); - enrollToken = prefs.getString('enrollToken'); - _postToken = prefs.getString('_postToken'); + // enrollToken = prefs.getString('pre_enrollment_data'); + // _postToken = prefs.getString('post_enrollment_data'); + + // Get the current token + final token = await tokenService.getCurrentToken(); + print('token - $token'); print('token check in'); - if((enrollToken != null && enrollToken!.isNotEmpty) || (_postToken != null && _postToken!.isNotEmpty)){ + if((token != null && token!.isNotEmpty)){ print('token check done'); _loadToken(); } else { @@ -120,17 +127,21 @@ class _hrDashboardState extends State Future _loadToken() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); - _postToken = prefs.getString('_postToken'); - enrollToken = prefs.getString('enrollToken'); + enrollToken = await tokenService.getCurrentToken(); + _postToken = await tokenService.getCurrentToken(); + print(enrollToken); + print(_postToken); + if (enrollToken != null && enrollToken!.isNotEmpty) { - Map? decodedToken = Jwt.parseJwt(enrollToken!); - enrollmentClient_id = decodedToken['client_id'].toString(); + // Map? decodedToken = Jwt.parseJwt(enrollToken!); + // enrollmentClient_id = decodedToken['client_id'].toString(); + enrollmentClient_id = prefs.getString('enrollmentClient_id'); enrollmentEmpClientBranchId = prefs.getString('enrollmentEmpClientBranchId'); enrollmentHrId = prefs.getString('enrollmentHrId'); - print("Pre decodedToken - $decodedToken"); + // print("Pre decodedToken - $decodedToken"); print("Pre enrollmentClient_id - $enrollmentClient_id"); print("Pre enrollmentEmpClientBranchId - $enrollmentEmpClientBranchId"); print("Pre enrollmentHrId - $enrollmentHrId"); @@ -163,7 +174,9 @@ class _hrDashboardState extends State clientLogo = prefs.getString('clientLogo'); clientName = prefs.getString('clientName'); } else { - getClientLogoAndDetails( + empClientId = prefs.getString('empClientId'); + empClientBranchId = prefs.getString('empClientBranchId'); + getClientLogoAndDetails(empClientId,empClientBranchId, enrollmentEmpClientBranchId, enrollmentClient_id, enrollToken); } getCashDepositDetails(enrollmentEmpClientBranchId, enrollmentClient_id, @@ -171,14 +184,15 @@ class _hrDashboardState extends State } if (_postToken != null && _postToken!.isNotEmpty) { - Map? postdecodedToken = Jwt.parseJwt(_postToken!); + // Map? postdecodedToken = Jwt.parseJwt(_postToken!); - empClientId = postdecodedToken['client_id'].toString(); + // empClientId = postdecodedToken['client_id'].toString(); + empClientId = prefs.getString('empClientId'); empClientBranchId = prefs.getString('empClientBranchId'); empHrId = prefs.getString('empHrId'); print('post empHrId- $empHrId'); - print('postdecodedToken- $postdecodedToken'); + // print('postdecodedToken- $postdecodedToken'); print('post empClientId- $empClientId'); print('post empClientBranchId- $empClientBranchId'); @@ -250,7 +264,10 @@ class _hrDashboardState extends State clientLogo = prefs.getString('clientLogo'); clientName = prefs.getString('clientName'); } else { - getClientLogoAndDetails(empClientBranchId, empClientId, _postToken); + empClientId = prefs.getString('empClientId'); + empClientBranchId = prefs.getString('empClientBranchId'); + getClientLogoAndDetails(empClientId,empClientBranchId, + enrollmentEmpClientBranchId, enrollmentClient_id, _postToken); } print("getCashDepositDetails"); } @@ -344,9 +361,10 @@ class _hrDashboardState extends State }).toList(); } - Future getClientLogoAndDetails(clintBranchId, clintID, token) async { + Future getClientLogoAndDetails(post_client_id, post_client_branch_id, + pre_client_branch_id, pre_client_id, token) async { var url = Uri.parse(Environment.apiUrl + - 'getClientDetails?client_id=$clintID&client_branch_id=$clintBranchId'); + 'getClientDetails?post_client_id=$post_client_id&post_branch_id=$post_client_branch_id&pre_client_id=$pre_client_id&pre_branch_id=$pre_client_branch_id'); try { var response = await http.get( url, @@ -525,6 +543,7 @@ class _hrDashboardState extends State width: double.infinity, // height: 75, height: MediaQuery.of(context).size.height * 0.12, + padding: Responsive.isDesktop(context) ? const EdgeInsets.only( top: 10, bottom: 10, left: 20, right: 20) @@ -542,32 +561,48 @@ class _hrDashboardState extends State height: 80, alignment: Alignment.centerLeft, child: Image.network( - clientLogo ?? '', - width: 80, // Set the width here - height: 60, // Set the height here - loadingBuilder: (BuildContext context, - Widget child, - ImageChunkEvent? loadingProgress) { - if (loadingProgress == null) return child; + Uri.encodeFull(clientLogo ?? ''), + width: 200, + height: 200, + fit: BoxFit.contain, + frameBuilder: (BuildContext context, Widget child, int? frame, bool wasSynchronouslyLoaded) { + if (wasSynchronouslyLoaded) { + return child; + } + return AnimatedOpacity( + opacity: frame == null ? 0 : 1, + duration: const Duration(milliseconds: 500), + curve: Curves.easeOut, + child: child, + ); + }, + loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) { + if (loadingProgress == null) { + return child; + } return Center( - child: CircularProgressIndicator( - value: loadingProgress.expectedTotalBytes != - null - ? loadingProgress - .cumulativeBytesLoaded / - loadingProgress.expectedTotalBytes! - : null, + child: SizedBox( + width: 30, + height: 30, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(Colors.grey), + value: loadingProgress.expectedTotalBytes != null + ? loadingProgress.cumulativeBytesLoaded / + (loadingProgress.expectedTotalBytes ?? 1) + : null, + ), ), ); }, - errorBuilder: (BuildContext context, Object error, - StackTrace? stackTrace) { - return Image.asset( - 'assets/Solid_gray.png', // Replace 'default_image.png' with your default image asset path - width: 80, - height: 60, - fit: BoxFit.cover, - ); + errorBuilder: (BuildContext context, Object error, StackTrace? stackTrace) { + return Text(''); + // Image.asset( + // 'assets/Solid_gray.png', + // width: 80, + // height: 60, + // fit: BoxFit.cover, + // ); }, ), ), diff --git a/lib/hrLogin.dart b/lib/hrLogin.dart index a6539f0..1167a26 100755 --- a/lib/hrLogin.dart +++ b/lib/hrLogin.dart @@ -49,12 +49,12 @@ class _MyPhoneState extends State { clearLocalStorageWhenStarts(fromData) async { print(fromData); - if(kIsWeb) { + // if(kIsWeb) { print('vndbbcbdskbvkj3'); final SharedPreferences prefs = await SharedPreferences.getInstance(); prefs.clear(); print('Local Storage Clear'); - } + // } } @@ -99,8 +99,6 @@ class _MyPhoneState extends State { if (userVerification) { final SharedPreferences prefs = await SharedPreferences.getInstance(); - // var enteredMobileNumber = mobileController.text; - // prefs.setString('empMobileNo', enteredMobileNumber); if (isEmailFieldVisible) { print('isEmailFieldVisible $isEmailFieldVisible'); prefs.setString('empEmail', emailController.text); @@ -111,12 +109,23 @@ class _MyPhoneState extends State { context, MaterialPageRoute( builder: (context) => MyEmailVerify( - email: emailController.text, // Pass the phone number + type: 'email', + value: emailController.text.trim(), // Pass the phone number ), ), ); } else { - _verifyPhoneNumber(); + ToastHelper.showSuccessToast( + context, 'Verification code sent to ${emailController.text}'); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => MyEmailVerify( + type: 'mobile', + value: mobileController.text.trim(), // Pass the phone number + ), + ), + ); } // ToastHelper.showSuccessToast(context, message); @@ -144,129 +153,129 @@ class _MyPhoneState extends State { } } - Future _verifyPhoneNumber() async { - var enteredMobileNumber = mobileController.text; - var countryCode = countryController.text; - print('${countryCode + enteredMobileNumber}'); - await _auth.verifyPhoneNumber( - phoneNumber: '${countryCode + enteredMobileNumber}', - timeout: const Duration(seconds: 60), - verificationCompleted: (PhoneAuthCredential credential) async { - await _auth.signInWithCredential(credential); - // ToastHelper.showSuccessToast(context, 'Verified Successfully!'); - // setState(() { - // _isLoading = false; - // }); - }, - verificationFailed: (FirebaseAuthException e) { - print('Verification Failed: ${e.code} - ${e.message}'); - String errorMessage; - if (e.code == 'invalid-app-credential') { - errorMessage = 'Invalid Credential. Please try again.'; - } else if (e.code == 'invalid-phone-number') { - errorMessage = 'The provided phone number is not valid.'; - } else if (e.code == 'too-many-requests') { - errorMessage = 'Too many requests. Try again later.'; - } else { - errorMessage = 'Verification Failed: ${e.message}'; - } - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(errorMessage), - duration: Duration(seconds: 2), - ), - ); - - ToastHelper.showSuccessToast(context, errorMessage); - setState(() { - _isLoading = false; - }); - }, - codeSent: (String verificationId, int? resendToken) async { - setState(() { - _verificationId = verificationId; - _resendToken = resendToken; - }); - - // When getting the verificationId - SharedPreferences prefs = await SharedPreferences.getInstance(); - prefs.setString('verificationId', _verificationId); - - ToastHelper.showSuccessToast( - context, 'Verification code sent to ${enteredMobileNumber}'); - - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => MyHrVerify( - verificationId: _verificationId, - mobileNumber: enteredMobileNumber, - resendToken: _resendToken, - onResendCode: _resendCode, // Pass the phone number - ), - ), - ); - setState(() { - _isLoading = false; - }); - }, - codeAutoRetrievalTimeout: (String verificationId) async { - setState(() { - _verificationId = verificationId; - }); - final prefs = await SharedPreferences.getInstance(); - await prefs.setString('verificationId', verificationId); - ToastHelper.showSuccessToast(context, 'Code auto-retrieval timed out.'); - setState(() { - _isLoading = false; - }); - }, - ); - } - - void _resendCode(String mobileNumber, int? resendToken) async { - await _auth.verifyPhoneNumber( - phoneNumber: '${countryController.text + mobileNumber}', - timeout: const Duration(seconds: 60), - forceResendingToken: resendToken, - verificationCompleted: (PhoneAuthCredential credential) async { - await _auth.signInWithCredential(credential); - }, - verificationFailed: (FirebaseAuthException e) { - if (e.code == 'invalid-phone-number') { - print('The provided phone number is not valid.'); - } - }, - codeSent: (String verificationId, int? resendToken) async { - final prefs = await SharedPreferences.getInstance(); - await prefs.setString('verificationId', verificationId); - setState(() { - _verificationId = verificationId; - _resendToken = resendToken; - }); - ToastHelper.showSuccessToast( - context, 'Verification code resent to ${mobileNumber}'); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => MyHrVerify( - verificationId: _verificationId, - mobileNumber: mobileNumber, - resendToken: _resendToken, - onResendCode: _resendCode, - ), - ), - ); - }, - codeAutoRetrievalTimeout: (String verificationId) async { - final prefs = await SharedPreferences.getInstance(); - await prefs.setString('verificationId', verificationId); - setState(() { - _verificationId = verificationId; - }); - }, - ); - } + // Future _verifyPhoneNumber() async { + // var enteredMobileNumber = mobileController.text; + // var countryCode = countryController.text; + // print('${countryCode + enteredMobileNumber}'); + // await _auth.verifyPhoneNumber( + // phoneNumber: '${countryCode + enteredMobileNumber}', + // timeout: const Duration(seconds: 60), + // verificationCompleted: (PhoneAuthCredential credential) async { + // await _auth.signInWithCredential(credential); + // // ToastHelper.showSuccessToast(context, 'Verified Successfully!'); + // // setState(() { + // // _isLoading = false; + // // }); + // }, + // verificationFailed: (FirebaseAuthException e) { + // print('Verification Failed: ${e.code} - ${e.message}'); + // String errorMessage; + // if (e.code == 'invalid-app-credential') { + // errorMessage = 'Invalid Credential. Please try again.'; + // } else if (e.code == 'invalid-phone-number') { + // errorMessage = 'The provided phone number is not valid.'; + // } else if (e.code == 'too-many-requests') { + // errorMessage = 'Too many requests. Try again later.'; + // } else { + // errorMessage = 'Verification Failed: ${e.message}'; + // } + // ScaffoldMessenger.of(context).showSnackBar( + // SnackBar( + // content: Text(errorMessage), + // duration: Duration(seconds: 2), + // ), + // ); + // + // ToastHelper.showSuccessToast(context, errorMessage); + // setState(() { + // _isLoading = false; + // }); + // }, + // codeSent: (String verificationId, int? resendToken) async { + // setState(() { + // _verificationId = verificationId; + // _resendToken = resendToken; + // }); + // + // // When getting the verificationId + // SharedPreferences prefs = await SharedPreferences.getInstance(); + // prefs.setString('verificationId', _verificationId); + // + // ToastHelper.showSuccessToast( + // context, 'Verification code sent to ${enteredMobileNumber}'); + // + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (context) => MyHrVerify( + // verificationId: _verificationId, + // mobileNumber: enteredMobileNumber, + // resendToken: _resendToken, + // onResendCode: _resendCode, // Pass the phone number + // ), + // ), + // ); + // setState(() { + // _isLoading = false; + // }); + // }, + // codeAutoRetrievalTimeout: (String verificationId) async { + // setState(() { + // _verificationId = verificationId; + // }); + // final prefs = await SharedPreferences.getInstance(); + // await prefs.setString('verificationId', verificationId); + // ToastHelper.showSuccessToast(context, 'Code auto-retrieval timed out.'); + // setState(() { + // _isLoading = false; + // }); + // }, + // ); + // } + // + // void _resendCode(String mobileNumber, int? resendToken) async { + // await _auth.verifyPhoneNumber( + // phoneNumber: '${countryController.text + mobileNumber}', + // timeout: const Duration(seconds: 60), + // forceResendingToken: resendToken, + // verificationCompleted: (PhoneAuthCredential credential) async { + // await _auth.signInWithCredential(credential); + // }, + // verificationFailed: (FirebaseAuthException e) { + // if (e.code == 'invalid-phone-number') { + // print('The provided phone number is not valid.'); + // } + // }, + // codeSent: (String verificationId, int? resendToken) async { + // final prefs = await SharedPreferences.getInstance(); + // await prefs.setString('verificationId', verificationId); + // setState(() { + // _verificationId = verificationId; + // _resendToken = resendToken; + // }); + // ToastHelper.showSuccessToast( + // context, 'Verification code resent to ${mobileNumber}'); + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (context) => MyHrVerify( + // verificationId: _verificationId, + // mobileNumber: mobileNumber, + // resendToken: _resendToken, + // onResendCode: _resendCode, + // ), + // ), + // ); + // }, + // codeAutoRetrievalTimeout: (String verificationId) async { + // final prefs = await SharedPreferences.getInstance(); + // await prefs.setString('verificationId', verificationId); + // setState(() { + // _verificationId = verificationId; + // }); + // }, + // ); + // } @override Widget build(BuildContext context) { diff --git a/lib/hrVerify.dart b/lib/hrVerify.dart index 57efc15..fa951d0 100755 --- a/lib/hrVerify.dart +++ b/lib/hrVerify.dart @@ -4,6 +4,7 @@ import 'package:google_fonts/google_fonts.dart'; import 'package:nhancepolicy/customAppBar/toastHelper.dart'; import 'package:nhancepolicy/models/environment.dart'; import 'package:nhancepolicy/service/api_service.dart'; +import 'package:nhancepolicy/service/token_storage_service.dart'; import 'package:pinput/pinput.dart'; import 'dart:async'; import 'package:flutter/gestures.dart'; @@ -14,6 +15,8 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:nhancepolicy/responsive.dart'; import 'package:jwt_decode/jwt_decode.dart'; +import 'branch/branch_selection_page.dart'; + class MyHrVerify extends StatefulWidget { final String verificationId; final String mobileNumber; @@ -139,35 +142,58 @@ class _MyVerifyState extends State { _isLoading = false; }); Map data = json.decode(response.body); - print('data: $data'); - _token = data['data']; - String status = data['status']; - // Directly access the post_enrollment data - Map post = data['post_enrollment']; - print('post: $post'); + print('Response data: $data'); - _postToken = post['data']; - String postStatus = post['status']; + // Extract enrollment data + List preEnrollmentData = data['data'] ?? []; + Map postEnrollment = data['post_enrollment'] ?? {}; + List postEnrollmentData = postEnrollment['data'] ?? []; - if (postStatus == 'success') { - setState(() { - _isLoading = false; - }); - postSuccessData(post, data); - } else if (status == 'success') { - setState(() { - _isLoading = false; - }); - enrollmentSuccessData(data); - } else { - setState(() { - _isLoading = false; - }); - ToastHelper.showErrorToast(context, 'Invalid OTP. Please try again'); - // Show a Snackbar if the OTP is invalid - print('Invalid OTP. Please try again'); - } + // Save to global storage + final tokenStorage = TokenStorageService(); + await tokenStorage.saveEnrollmentData( + preEnrollmentData, + postEnrollmentData, + ); + + // Navigate to branch selection + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => BranchSelectionPage(), + ), + ); + + // print('data: $data'); + // _token = data['data']; + // String status = data['status']; + // + // // Directly access the post_enrollment data + // Map post = data['post_enrollment']; + // print('post: $post'); + // + // _postToken = post['data']; + // String postStatus = post['status']; + // + // if (postStatus == 'success') { + // setState(() { + // _isLoading = false; + // }); + // postSuccessData(post, data); + // } else if (status == 'success') { + // setState(() { + // _isLoading = false; + // }); + // enrollmentSuccessData(data); + // } else { + // setState(() { + // _isLoading = false; + // }); + // ToastHelper.showErrorToast(context, 'Invalid OTP. Please try again'); + // // Show a Snackbar if the OTP is invalid + // print('Invalid OTP. Please try again'); + // } } else { setState(() { _isLoading = false; diff --git a/lib/main.dart b/lib/main.dart index feecec8..901c09a 100755 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:nhancepolicy/addons.dart'; +import 'package:nhancepolicy/branch/branch_selection_page.dart'; import 'package:nhancepolicy/empReview.dart'; import 'package:nhancepolicy/empDetails.dart'; import 'package:nhancepolicy/excel_verification.dart'; @@ -10,6 +11,7 @@ import 'package:nhancepolicy/hrVerify.dart'; import 'package:nhancepolicy/models/environment.dart'; import 'package:nhancepolicy/phone.dart'; import 'package:nhancepolicy/postFileUpload.dart'; +import 'package:nhancepolicy/service/token_storage_service.dart'; import 'package:nhancepolicy/verify.dart'; import 'package:nhancepolicy/home.dart'; import 'package:nhancepolicy/hrDashboard.dart'; @@ -28,8 +30,23 @@ Future main() async { // print('Local Storage cleared by window'); // html.window.localStorage.clear(); // }); + // Ask confirmation (browser may or may not show it again) + // html.window.onBeforeUnload.listen((event) { + // final e = event as html.BeforeUnloadEvent; + // e.preventDefault(); + // e.returnValue = ''; + // }); + + // Always clear when user leaves (refresh/close) + // html.window.onUnload.listen((event) { + // html.window.localStorage.clear(); + // }); // await dotenv.load(fileName: Environment.fileName); WidgetsFlutterBinding.ensureInitialized(); + + // Initialize token storage + await TokenStorageService().initialize(); + await Firebase.initializeApp( options: const FirebaseOptions( apiKey: 'AIzaSyCSvDM5fG2blDBE69Cae3S-iYRwwNBy7xo', @@ -57,7 +74,9 @@ Future main() async { routes: { 'phone': (context) => MyPhone(), 'mailVerify': (context) => MyEmailVerify( - email: '', + type: '', + value: '', + ), 'verify': (context) => MyVerify( verificationId: '', @@ -130,6 +149,7 @@ Future main() async { cardPolicy_ExpDate: '', ), 'oldPolicy': (context) => oldPolicy(), + 'branchSelection': (context) => BranchSelectionPage(), }, )); } diff --git a/lib/service/api_service.dart b/lib/service/api_service.dart index 3b3a6f5..d9309be 100755 --- a/lib/service/api_service.dart +++ b/lib/service/api_service.dart @@ -19,8 +19,8 @@ class ApiService { Future _initializeToken() async { final prefs = await SharedPreferences.getInstance(); _token = prefs.getString('token') ?? ''; - final hrprefs = await SharedPreferences.getInstance(); - _hrtoken = hrprefs.getString('hrtoken') ?? ''; + // final hrprefs = await SharedPreferences.getInstance(); + // _hrtoken = hrprefs.getString('hrtoken') ?? ''; } Future getTokenLoadAPI(token) async { diff --git a/lib/service/token_storage_service.dart b/lib/service/token_storage_service.dart new file mode 100644 index 0000000..54f0aea --- /dev/null +++ b/lib/service/token_storage_service.dart @@ -0,0 +1,157 @@ +import 'package:shared_preferences/shared_preferences.dart'; +import 'dart:convert'; + +class TokenStorageService { + static final TokenStorageService _instance = TokenStorageService._internal(); + factory TokenStorageService() => _instance; + TokenStorageService._internal(); + + // Storage keys + static const String _preEnrollmentKey = 'pre_enrollment_data'; + static const String _postEnrollmentKey = 'post_enrollment_data'; + static const String _selectedBranchKey = 'selected_branch'; + static const String _decodedTokenKey = 'decoded_token'; + + // In-memory cache + List? _preEnrollmentData; + List? _postEnrollmentData; + Map? _selectedBranch; + Map? _decodedToken; + + // Initialize - Load data from storage + Future initialize() async { + final prefs = await SharedPreferences.getInstance(); + + final preData = prefs.getString(_preEnrollmentKey); + if (preData != null) { + _preEnrollmentData = json.decode(preData); + } + + final postData = prefs.getString(_postEnrollmentKey); + if (postData != null) { + _postEnrollmentData = json.decode(postData); + } + + final branchData = prefs.getString(_selectedBranchKey); + if (branchData != null) { + _selectedBranch = json.decode(branchData); + } + + final tokenData = prefs.getString(_decodedTokenKey); + if (tokenData != null) { + _decodedToken = json.decode(tokenData); + } + } + + // Save enrollment data + Future saveEnrollmentData(List preData, List postData) async { + _preEnrollmentData = preData; + _postEnrollmentData = postData; + + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(_preEnrollmentKey, json.encode(preData)); + await prefs.setString(_postEnrollmentKey, json.encode(postData)); + } + + // Get combined unique branches + List> getCombinedBranches() { + List> combined = []; + Set seenTokens = {}; + Set seenIds = {}; + + // Add pre-enrollment data + if (_preEnrollmentData != null) { + for (var item in _preEnrollmentData!) { + String token = item['token']?.toString() ?? ''; + String uniqueId = '${item['id']}_${item['client_id']}_${item['client_branch_id']}'; + + if (seenIds.contains(uniqueId)) continue; + if (token.isNotEmpty && seenTokens.contains(token)) continue; + + if (token.isNotEmpty) seenTokens.add(token); + seenIds.add(uniqueId); + + combined.add({...item, 'enrollment_type': 'pre'}); + } + } + + // Add post-enrollment data + if (_postEnrollmentData != null) { + for (var item in _postEnrollmentData!) { + String token = item['token']?.toString() ?? ''; + String uniqueId = '${item['id']}_${item['client_id']}_${item['client_branch_id']}'; + + if (seenIds.contains(uniqueId)) continue; + if (token.isNotEmpty && seenTokens.contains(token)) continue; + + if (token.isNotEmpty) seenTokens.add(token); + seenIds.add(uniqueId); + + combined.add({...item, 'enrollment_type': 'post'}); + } + } + + return combined; + } + + // Save selected branch and decode token + Future saveSelectedBranch(Map branch) async { + _selectedBranch = branch; + + String token = branch['token']?.toString() ?? ''; + if (token.isNotEmpty) { + _decodedToken = _decodeJWT(token); + } else { + _decodedToken = null; + } + + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(_selectedBranchKey, json.encode(branch)); + + if (_decodedToken != null) { + await prefs.setString(_decodedTokenKey, json.encode(_decodedToken)); + } + } + + // Decode JWT token + Map? _decodeJWT(String token) { + try { + final parts = token.split('.'); + if (parts.length != 3) return null; + + final payload = parts[1]; + var normalized = base64Url.normalize(payload); + var decoded = utf8.decode(base64Url.decode(normalized)); + + return json.decode(decoded); + } catch (e) { + print('Error decoding token: $e'); + return null; + } + } + + // Getters + Map? getSelectedBranch() => _selectedBranch; + Map? getDecodedToken() => _decodedToken; + String? getCurrentToken() => _selectedBranch?['token']; + + bool isLoggedIn() { + return _selectedBranch != null && + _selectedBranch!['token'] != null && + _selectedBranch!['token'].toString().isNotEmpty; + } + + // Clear all data (logout) + Future clearAll() async { + _preEnrollmentData = null; + _postEnrollmentData = null; + _selectedBranch = null; + _decodedToken = null; + + final prefs = await SharedPreferences.getInstance(); + await prefs.remove(_preEnrollmentKey); + await prefs.remove(_postEnrollmentKey); + await prefs.remove(_selectedBranchKey); + await prefs.remove(_decodedTokenKey); + } +} \ No newline at end of file