diff --git a/lib/customAppBar/customAppBar.dart b/lib/customAppBar/customAppBar.dart index 52d14e4..d9b5b91 100755 --- a/lib/customAppBar/customAppBar.dart +++ b/lib/customAppBar/customAppBar.dart @@ -8,13 +8,25 @@ import 'package:nhance_app_pwa/customAppBar/responsive.dart'; import '../pages/postEnrollment/service/api_service.dart'; import '../pages/service/SessionManager.dart'; import '../pages/service/popup_helper.dart'; +class CustomAppBar extends StatefulWidget implements PreferredSizeWidget { + @override + _CustomAppBarState createState() => _CustomAppBarState(); -class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { + @override + Size get preferredSize => Size.fromHeight(kToolbarHeight); +} +class _CustomAppBarState extends State { @override Size get preferredSize => Size.fromHeight(kToolbarHeight); late ApiService apiService; final session = SessionManager(); + @override + void initState() { + super.initState(); + apiService = ApiService(context); + } + Future logout(BuildContext context) async { final prefs = await SharedPreferences.getInstance(); final String? token = prefs.getString('_postToken'); @@ -28,107 +40,179 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { @override Widget build(BuildContext context) { - apiService = ApiService(context); final sw = MediaQuery.of(context).size.width; - final isPoliciesPage = Uri.base.fragment == 'policies'; - return ClipRRect( - borderRadius: Responsive.isDesktop(context) || isPoliciesPage - ? BorderRadius.zero - : BorderRadius.only( - bottomLeft: Radius.circular(32.0), - bottomRight: Radius.circular(32.0), + return Scaffold( + backgroundColor: Color(0xFFFFFCE5), // Set background color for AppBar + appBar: PreferredSize( + preferredSize: widget.preferredSize, + child: SafeArea( + child: Container( + // padding: Responsive.isDesktop(context) + // ? EdgeInsets.symmetric(horizontal: 46.0) + // : EdgeInsets.symmetric(horizontal: 0), + padding: EdgeInsets.symmetric( + horizontal: MediaQuery.of(context).size.width * + (Responsive.isDesktop(context) ? 0.03 : 0.02), ), - child: AppBar( - backgroundColor: const Color(0xFFFFFBDE), - elevation: 0, // Set background color for AppBar - toolbarHeight: kToolbarHeight, - titleSpacing: 0.0, - automaticallyImplyLeading: false, - title: Padding( - padding: Responsive.isDesktop(context) - ? EdgeInsets.symmetric(horizontal: 16.0) - : EdgeInsets.symmetric(horizontal: 10), - child: Row( - children: [ - // Logo Column - Expanded( - flex: Responsive.isDesktop(context) ? 3 : 9, - child: Row( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // Logo Column + // Expanded( + // // flex: Responsive.isDesktop(context) ? 1 : 9, + // child: + Row( mainAxisAlignment: Responsive.isDesktop(context) ? MainAxisAlignment.spaceEvenly - : MainAxisAlignment - .start, // Adjust the alignment as needed + : MainAxisAlignment.start, children: [ Container( - // margin: EdgeInsets.only( - // top: 10, bottom: 10, left: 10, right: 10), - // width: Responsive.isDesktop(context) ? 150 : 130, - // height: Responsive.isDesktop(context) ? 31 : 130, - child: SvgPicture.asset( - 'assets/nhance_client_logo.svg', - width: 150, + margin: EdgeInsets.only(top: 10, bottom: 10), + width: 150, + height: 150, + child: Image.asset( + 'assets/nhance_client_logo.png', + fit: BoxFit.contain, ), - // child: Image.asset( - // 'assets/nhance_logo.png', - // width: 250, - // ), ), ], ), - ), - // AdaptiveNavBar Column - if (Responsive.isDesktop(context)) - Expanded( - flex: Responsive.isDesktop(context) ? 9 : 3, - child: AdaptiveNavBar( - screenWidth: sw, - backgroundColor: const Color(0xFFFFFBDE), - leading: - Container(), // Set an empty container as we have the logo separately - title: Text(''), - navBarItems: [ - NavBarItem( - text: "Home", - onTap: () { - context.push('/home'); - }, - ), - NavBarItem( - text: "Claims", - onTap: () { - context.push('/claims'); - }, - ), - NavBarItem( - text: "Help", - onTap: () { - context.push('/help'); - }, - ), - NavBarItem( - text: "Wellness", - onTap: () { - // Wellness tab clicked → show popup - PopupHelper.showRedirectPopup( - context: context, - apiService: apiService, - empPrimaryId: session.empPrimaryId, - ); - }, - ), - NavBarItem( - text: "Profile", - onTap: () { - context.push('/profile'); - }, - ), - ], - ), + // ), + // AdaptiveNavBar Column + + // Expanded( + // // flex: Responsive.isDesktop(context) ? 1 : 9, + // child: + if(Responsive.isDesktop(context)) + Row( + mainAxisSize: MainAxisSize.min, + children: [ + NavBarItem( + text: "Home", + onTap: () { + context.push('/home'); + }, + ), + NavBarItem( + text: "Claims", + onTap: () { + context.push('/claims'); + }, + ), + NavBarItem( + text: "Help", + onTap: () { + context.push('/help'); + }, + ), + NavBarItem( + text: "Wellness", + onTap: () { + // context.push('/wellness'); + // Wellness tab clicked → show popup + PopupHelper.showRedirectPopup( + context: context, + apiService: apiService, + empPrimaryId: session.empPrimaryId, + ); + }, + ), + NavBarItem( + text: "Profile", + onTap: () { + context.push('/profile'); + }, + ), + ], ), - ], + // ) + + // Expanded( + // flex: Responsive.isDesktop(context) ? 9 : 3, + // child: AdaptiveNavBar( + // screenWidth: sw, + // backgroundColor: Color(0xFFFFFCE5), + // leading: + // Container(), // Set an empty container as we have the logo separately + // title: Text(''), + // navBarItems: [ + // // if (Responsive.isDesktop(context)) + // // if (hideInactiveStatus) + // // NavBarItem( + // // text: "Inactive Policy", + // // onTap: () { + // // Navigator.pushNamed(context, 'oldPolicy'); + // // }, + // // ), + // // if (showBackToHR) + // 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 { + // logout(context); + // }, + // ), + // ], + // ), + // ), + ], + ), ), ), ), ); } + } +class NavBarItem extends StatelessWidget { + final String text; + final VoidCallback? onTap; + + const NavBarItem({ + Key? key, + required this.text, + this.onTap, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return InkWell( + borderRadius: BorderRadius.circular(6), + onTap: onTap, + hoverColor: const Color(0xFFF4F3E7), // subtle hover background + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + child: Text( + text, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Colors.black87, + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/pages/changePassword.dart b/lib/pages/changePassword.dart index b211920..77fec03 100644 --- a/lib/pages/changePassword.dart +++ b/lib/pages/changePassword.dart @@ -41,12 +41,25 @@ class _changesPasswordState extends State { bool _obscureNewPassword = true; bool _obscureConfirmPassword = true; late SessionManager session; + bool hasMinLength = false; + bool hasUpperLower = false; + bool hasNumber = false; + bool hasSpecialChar = false; + bool get isPasswordValid => hasMinLength && hasUpperLower && hasNumber && hasSpecialChar; @override void initState() { super.initState(); } + void validatePassword(String password) { + setState(() { + hasMinLength = password.length >= 8; + hasUpperLower = RegExp(r'(?=.*[A-Za-z])').hasMatch(password); + hasNumber = RegExp(r'(?=.*\d)').hasMatch(password); + hasSpecialChar = RegExp(r'(?=.*[@$!%*#?&])').hasMatch(password); + }); + } Future resetYourPassword() async { final oldpassword = oldPasswordController.text.trim(); @@ -354,7 +367,7 @@ class _changesPasswordState extends State { MainAxisAlignment.center, children: [ Text( - "Welcome to Nhance", + "Change Password", style: GoogleFonts.poppins( fontSize: 16, fontWeight: FontWeight.bold, @@ -378,7 +391,7 @@ class _changesPasswordState extends State { children: [ Expanded( child: Text( - "Login with your to review and enroll for exciting health benefits for you and your family", + "To change your password, please fill in the fields below.", style: GoogleFonts.poppins( fontSize: 12, color: Color(0xFF000000)), @@ -466,70 +479,58 @@ class _changesPasswordState extends State { const SizedBox(height: 10), Container( height: 55, - margin: Responsive.isDesktop( - context) - ? const EdgeInsets - .symmetric( - horizontal: 150) - : const EdgeInsets - .symmetric( - horizontal: 0), + margin: Responsive.isDesktop(context) + ? const EdgeInsets.symmetric(horizontal: 150) + : const EdgeInsets.symmetric(horizontal: 0), decoration: BoxDecoration( - border: Border.all( - width: 1, - color: Colors.grey), - borderRadius: - BorderRadius.circular( - 10), + border: Border.all(width: 1, color: Colors.grey), + borderRadius: BorderRadius.circular(10), ), child: TextFormField( - controller: - newPasswordController, - obscureText: - _obscureNewPassword, - textAlignVertical: - TextAlignVertical - .center, + controller: newPasswordController, + obscureText: _obscureNewPassword, + onChanged: validatePassword, + textAlignVertical: TextAlignVertical.center, decoration: InputDecoration( border: InputBorder.none, - hintText: - "New Password", - contentPadding: - const EdgeInsets - .symmetric( - horizontal: 10), + hintText: "New Password", + contentPadding: const EdgeInsets.symmetric(horizontal: 10), suffixIcon: IconButton( icon: Icon( - _obscureNewPassword - ? Icons - .visibility_off - : Icons - .visibility, + _obscureNewPassword ? Icons.visibility_off : Icons.visibility, color: Colors.grey, ), onPressed: () { setState(() { - _obscureNewPassword = - !_obscureNewPassword; + _obscureNewPassword = !_obscureNewPassword; }); }, ), ), - validator: (value) { - if (value == null || - value.isEmpty) { - return 'Please enter your new password'; - } - if (value.length < 6) { - return 'Password must be at least 6 characters'; - } - if (!RegExp( - r'^(?=.*[A-Z])(?=.*[0-9]).{6,}$') - .hasMatch(value)) { - return 'Include at least 1 uppercase letter and 1 number'; - } - return null; - }, + ), + ), + const SizedBox(height: 8), + // 🔹 VALIDATION LIST + Padding( + padding: Responsive.isDesktop(context) + ? const EdgeInsets.symmetric(horizontal: 150) + : const EdgeInsets.symmetric(horizontal: 5), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded(child: _buildCheckItem(hasMinLength, "Minimum 8 characters")), + Expanded(child: _buildCheckItem(hasSpecialChar, "1 special character")), + ], + ), + Row( + children: [ + Expanded(child: _buildCheckItem(hasUpperLower, "1 UPPER or lower case")), + Expanded(child: _buildCheckItem(hasNumber, "1 numerical")), + ], + ), + ], ), ), const SizedBox(height: 10), @@ -767,4 +768,28 @@ class _changesPasswordState extends State { )), ))); } + + Widget _buildCheckItem(bool status, String text) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: Row( + children: [ + Icon( + status ? Icons.check : Icons.close, + color: status ? Colors.green : Colors.red, + size: 18, + ), + const SizedBox(width: 6), + Text( + text, + style: TextStyle( + color: status ? Colors.green : Colors.red, + fontSize: 14, + ), + ), + ], + ), + ); + } + } diff --git a/lib/pages/email_verify.dart b/lib/pages/email_verify.dart index 01ed784..777a6fc 100755 --- a/lib/pages/email_verify.dart +++ b/lib/pages/email_verify.dart @@ -442,9 +442,9 @@ class _MyEmailVerifyState extends State { // final SharedPreferences prefs = await SharedPreferences.getInstance(); // prefs.setString('empEmailid', widget.email); ToastHelper.showSuccessToast(context, 'Successfully Login'); - // checkPassword(context,session.empEmailCorporate,session.client_id,'home'); + checkPassword(context,session.empEmailCorporate,session.client_id,'home'); // if (emp_status == 'enrolled' || emp_status == 'active') { - context.go('/home'); + // context.go('/home'); // Navigator.pushReplacementNamed(context, 'home'); // } else { // Navigator.pushReplacementNamed(context, 'empDetails'); @@ -499,11 +499,11 @@ class _MyEmailVerifyState extends State { } else { if (_preToken != null && _preToken.isNotEmpty) { ToastHelper.showSuccessToast(context, 'Successfully Login'); - // checkPassword(context,session.enrollmentEmailCorporate,session.enrollmentClient_id,'empDetails'); + checkPassword(context,session.enrollmentEmailCorporate,session.enrollmentClient_id,'empDetails'); // if (emp_status == 'enrolled' || emp_status == 'active') { // Navigator.pushReplacementNamed(context, 'home'); // } else { - context.go('/empDetails'); + // context.go('/empDetails'); // Navigator.pushReplacementNamed(context, 'empDetails'); // } } diff --git a/lib/pages/login.dart b/lib/pages/login.dart index 2b04f5a..e0d52ab 100755 --- a/lib/pages/login.dart +++ b/lib/pages/login.dart @@ -6,6 +6,8 @@ import 'package:flutter/services.dart'; import 'package:go_router/go_router.dart'; import 'package:http/http.dart' as http; import 'package:nhance_app_pwa/pages/email_verify.dart'; +import 'package:nhance_app_pwa/pages/service/SessionManager.dart'; +import 'package:nhance_app_pwa/pages/service/TokenService.dart'; import 'package:nhance_app_pwa/pages/verify.dart'; import 'dart:convert'; import 'dart:io'; @@ -15,7 +17,8 @@ import '../customAppBar/responsive.dart'; import '../customAppBar/toastHelper.dart'; import '../models/environment.dart'; import '../models/platform_helper_mobile.dart' - if (dart.library.html) '../models/platform_helper_other.dart'; +if (dart.library.html) '../models/platform_helper_other.dart'; +import 'package:pinput/pinput.dart'; class login extends StatefulWidget { const login({Key? key}); @@ -25,20 +28,57 @@ class login extends StatefulWidget { } class _loginState extends State { + final TextEditingController emailMobileController = TextEditingController(); final TextEditingController emailController = TextEditingController(); + final TextEditingController passwordController = TextEditingController(); + final TextEditingController resetPasswordController = TextEditingController(); + final TextEditingController confirmPasswordController = + TextEditingController(); + final TextEditingController _otpController = TextEditingController(); TextEditingController countryController = TextEditingController(); TextEditingController mobileController = TextEditingController(); final _formKey = GlobalKey(); bool _isCheckingToken = false; dynamic empMobileNo; dynamic empEmailid; - - // final FirebaseAuth _auth = FirebaseAuth.instance; - + int switcherStatus = 1; + int selectedIndex = 1; + dynamic _preToken; + dynamic _postToken; + dynamic clientName; + dynamic clientLogo; late String _verificationId; int? _resendToken; bool _isLoading = false; bool isEmailFieldVisible = false; + bool _obscurePassword = true; + bool _resetObscurePassword = true; + bool _obscureConfirmPassword = true; + late SessionManager session; + bool clickedForgotPassword = false; + bool resetPasswordEnable = false; + bool otpFieldShow = false; + bool otpValueStatus = false; + final defaultPinTheme = PinTheme( + width: 56, + height: 56, + textStyle: TextStyle( + fontSize: 20, + color: Color.fromRGBO(30, 60, 87, 1), + fontWeight: FontWeight.w600, + ), + decoration: BoxDecoration( + border: Border.all(color: Color.fromRGBO(234, 239, 243, 1)), + borderRadius: BorderRadius.circular(20), + ), + ); + + bool hasMinLength = false; + bool hasUpperLower = false; + bool hasNumber = false; + bool hasSpecialChar = false; + bool get isPasswordValid => hasMinLength && hasUpperLower && hasNumber && hasSpecialChar; + @override void initState() { @@ -47,6 +87,15 @@ class _loginState extends State { // checkLoginPinOnMobile(); } + void validatePassword(String password) { + setState(() { + hasMinLength = password.length >= 8; + hasUpperLower = RegExp(r'(?=.*[A-Za-z])').hasMatch(password); + hasNumber = RegExp(r'(?=.*\d)').hasMatch(password); + hasSpecialChar = RegExp(r'(?=.*[@$!%*#?&])').hasMatch(password); + }); + } + Future checkLoginPinOnMobile() async { if (isMobilePlatform()) { final SharedPreferences prefs = await SharedPreferences.getInstance(); @@ -147,18 +196,35 @@ class _loginState extends State { Future verifyMobileAndEmailNumber() async { try { if (_formKey.currentState!.validate()) { + setState(() { _isLoading = true; }); + String input = emailMobileController.text.trim(); + + bool isEmail = RegExp(r'^[^@]+@[^@]+\.[^@]+$').hasMatch(input); + bool isMobile = RegExp(r'^[0-9]{10}$').hasMatch(input); + + if (isEmail) { + setState(() { + isEmailFieldVisible = true; + }); + print("User entered Email: $input"); + } else if (isMobile) { + isEmailFieldVisible = false; + print("User entered Mobile: $input"); + } + + // Determine the API and the payload based on the visible field String apiEndpoint = isEmailFieldVisible ? Environment.apiUrlEnrollment + 'verifyEmployeeEmailId' : Environment.apiUrlEnrollment + 'verifyEmployeeNumber'; Map payload = isEmailFieldVisible - ? {'email': emailController.text} - : {'mobile_number': mobileController.text}; + ? {'email': emailMobileController.text} + : {'mobile_number': emailMobileController.text}; // var enteredMobileNumber = mobileController.text; final response = await http.post( @@ -175,20 +241,20 @@ class _loginState extends State { String message = data['data']['message']; if (userVerification) { final SharedPreferences prefs = - await SharedPreferences.getInstance(); + await SharedPreferences.getInstance(); // var enteredMobileNumber = mobileController.text; // prefs.setString('empMobileNo', enteredMobileNumber); ToastHelper.showSuccessToast( - context, 'Verification code sent to ${emailController.text}'); + context, 'Verification code sent to ${emailMobileController.text}'); if (isEmailFieldVisible) { print('isEmailFieldVisible $isEmailFieldVisible'); // prefs.setString('empEmail', emailController.text); - print('${emailController.text}'); + print('${emailMobileController.text}'); context.push( '/mailVerify', extra: { 'type': 'email', - 'value': emailController.text.trim(), + 'value': emailMobileController.text.trim(), }, ); } else { @@ -197,7 +263,7 @@ class _loginState extends State { '/mailVerify', extra: { 'type': 'mobile', - 'value': mobileController.text.trim(), + 'value': emailMobileController.text.trim(), }, ); } @@ -374,11 +440,552 @@ class _loginState extends State { // ); // } + //starts Login with UserName and Pasword + + void loginWithUsernameAndPw() async { + try { + if (_formKey.currentState!.validate()) { + setState(() { + _isLoading = true; + }); + + // print('EMAIL PARAMS : ${widget.email} - OTP : $otp'); + final Map payload = { + 'email_id': emailController.text, + 'password': passwordController.text + }; + + final response = await http.post( + Uri.parse(Environment.apiUrlEnrollment + 'verifyPassword'), + body: json.encode(payload), + headers: { + HttpHeaders.contentTypeHeader: 'application/json', + }, + ); + print('response : ${response.statusCode}'); + if (response.statusCode == 200) { + setState(() { + _isLoading = false; + }); + Map data = json.decode(response.body); + print('data: $data'); + + /// Check API response status first + if (data['status'] == 'Invalid Password') { + final SharedPreferences prefs = + await SharedPreferences.getInstance(); + prefs.clear(); + ToastHelper.showErrorToast( + context, data['message'] ?? 'Invalid Password'); + print('API error → ${data['message']}'); + return; // stop execution + } + + // --- Extract tokens safely --- + String? preToken; + if (data['data'] is String) { + preToken = data['data']; + } + + String? postToken; + if (data['post_enrollment'] != null && + data['post_enrollment']['data'] is String) { + postToken = data['post_enrollment']['data']; + } + + // Save tokens + await TokenService.saveTokens( + preToken: preToken, postToken: postToken); + print('Tokens saved → preToken: $preToken, postToken: $postToken'); + + _preToken = preToken; + String status = data['status']; + + // Directly access the post_enrollment data + Map post = data['post_enrollment']; + print('post: $post'); + + _postToken = postToken; + 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; + }); + final SharedPreferences prefs = + await SharedPreferences.getInstance(); + prefs.clear(); + ToastHelper.showErrorToast( + context, 'Invalid Passsword. Please try again'); + // Show a Snackbar if the OTP is invalid + print('Invalid Password. Please try again'); + } + } else { + setState(() { + _isLoading = false; + }); + final SharedPreferences prefs = await SharedPreferences.getInstance(); + prefs.clear(); + ToastHelper.showWarningToast(context, 'Something went wrong'); + throw Exception('Failed to verify OTP'); + } + } + } catch (e) { + setState(() { + _isLoading = false; + }); + final SharedPreferences prefs = await SharedPreferences.getInstance(); + prefs.clear(); + print('Error: $e'); + ToastHelper.showWarningToast(context, 'Something went wrong'); + // Show a Snackbar if there's an error while verifying OTP + print('Failed to verify OTP. Please try again.'); + } + } + + void postSuccessData(post, data) async { + String status = data['status']; + String postStatus = post['status']; + + if (postStatus == 'success') { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + await SessionManager().initializeFromPostToken(post['data']); + session = await SessionManager(); + print('Successfully Login'); + } + + if (status == 'success') { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + await SessionManager().initializeFromPreToken(data['data']); + session = await SessionManager(); + getClientLogoAndDetails(); + } + if (_postToken != null && _postToken.isNotEmpty) { + ToastHelper.showSuccessToast(context, 'Successfully Login'); + context.go('/home'); + // Navigator.pushReplacementNamed(context, 'home'); + } else { + context.go('/empDetails'); + // Navigator.pushReplacementNamed(context, 'empDetails'); + } + } + + void enrollmentSuccessData(data) async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + await SessionManager().initializeFromPreToken(data['data']); + session = await SessionManager(); + getClientLogoAndDetails(); + print('Successfully Login'); + print(isMobilePlatform()); + if (_preToken != null && _preToken.isNotEmpty) { + ToastHelper.showSuccessToast(context, 'Successfully Login'); + context.go('/home'); + // Navigator.pushReplacementNamed(context, 'home'); + } else { + context.go('/empDetails'); + // Navigator.pushReplacementNamed(context, 'empDetails'); + } + } + + Future getClientLogoAndDetails() async { + var url = Uri.parse(Environment.apiUrlEnrollment + + 'getClientDetails?post_client_id=${session.client_id}&post_branch_id=${session.empClientBranchId}&pre_client_id=${session.enrollmentClient_id}&pre_branch_id=${session.enrollmentEmpClientBranchId}'); + try { + var response = await http.get( + url, + headers: { + 'Authorization': + 'Bearer $_preToken', // Add token to the Authorization header + }, + ); + if (response.statusCode == 200) { + // print('response.statusCode == 200'); + Map data = json.decode(response.body); + // print(data); + + if (data.containsKey('data')) { + dynamic clientDetails = data['data']; + final SharedPreferences prefs = await SharedPreferences.getInstance(); + await prefs.setString( + 'clientLogo', clientDetails['client']['client_logo']); + await prefs.setString( + 'clientName', clientDetails['client']['client_name']); + await prefs.setString( + 'addon_subheading', clientDetails['client']['addon_subheading']); + setState(() { + // dynamic clientDetails = data['data']; + // print(clientDetails); + clientName = clientDetails['client']['client_name']; + print(clientName); + clientLogo = clientDetails['client']['client_logo']; + print(clientLogo); + }); + } else { + // Handle other status messages if needed + // ToastHelper.showErrorToast( + // context, 'API request failed with status: ${data['status']}'); + print('API request failed with status: ${data['status']}'); + } + } else { + // Handle other status codes + // ToastHelper.showErrorToast( + // context, 'Request failed with status: ${response.statusCode}'); + print('Request failed with status: ${response.statusCode}'); + } + } catch (e) { + // Handle exceptions + print('Exception occurred: $e'); + } + } + + Future forgotPassword() async { + setState(() { + clickedForgotPassword = true; + passwordController.text = ''; + passwordController.clear(); + }); + } + + Future mailVerify() async { + try { + if (_formKey.currentState!.validate()) { + setState(() { + _isLoading = true; + }); + + // Determine the API and the payload based on the visible field + String apiEndpoint = + Environment.apiUrlEnrollment + 'verifyEmployeeEmailId'; + + Map payload = {'email': emailController.text}; + + // var enteredMobileNumber = mobileController.text; + final response = await http.post( + Uri.parse(apiEndpoint), + body: json.encode(payload), + headers: { + HttpHeaders.contentTypeHeader: 'application/json', + }, + ); + + if (response.statusCode == 200) { + Map data = json.decode(response.body); + bool userVerification = data['data']['user_verification']; + String message = data['data']['message']; + if (userVerification) { + final SharedPreferences prefs = + await SharedPreferences.getInstance(); + // var enteredMobileNumber = mobileController.text; + // prefs.setString('empMobileNo', enteredMobileNumber); + ToastHelper.showSuccessToast( + context, 'OTP send to registered email'); + + setState(() { + otpFieldShow = true; + _isLoading = false; + }); + // ToastHelper.showSuccessToast(context, message); + } else { + setState(() { + _isLoading = false; + }); + ToastHelper.showErrorToast(context, message); + print('Invalid mobile number'); + } + } else { + setState(() { + _isLoading = false; + }); + ToastHelper.showErrorToast(context, 'Something went wrong'); + throw Exception('Failed to verify mobile number'); + } + } + } catch (e) { + setState(() { + _isLoading = false; + }); + ToastHelper.showErrorToast(context, 'Something went wrong'); + print('Error: $e'); + } + + } + + Future otpVerify() async { + try { + if (_formKey.currentState!.validate()) { + setState(() { + _isLoading = true; + }); + + // Determine the API and the payload based on the visible field + String apiEndpoint = Environment.apiUrlEnrollment + 'verifyOtp'; + Map payload = { + 'email_id': emailController.text, + 'otp': _otpController.text + }; + + // var enteredMobileNumber = mobileController.text; + final response = await http.post( + Uri.parse(apiEndpoint), + body: json.encode(payload), + headers: { + HttpHeaders.contentTypeHeader: 'application/json', + }, + ); + + if (response.statusCode == 200) { + Map data = json.decode(response.body); + print(data); + String? verificationStatus = data['status']; + print(verificationStatus); + String? message = data['message']; + print(message); + if (verificationStatus == 'success') { + final SharedPreferences prefs = + await SharedPreferences.getInstance(); + // var enteredMobileNumber = mobileController.text; + // prefs.setString('empMobileNo', enteredMobileNumber); + ToastHelper.showSuccessToast(context, message!); + + setState(() { + otpValueStatus = false; + otpFieldShow = false; + clickedForgotPassword = false; + resetPasswordEnable = true; + _isLoading = false; + }); + // ToastHelper.showSuccessToast(context, message); + } else { + setState(() { + otpValueStatus = true; + _isLoading = false; + }); + ToastHelper.showErrorToast(context, message!); + print('Invalid mobile number'); + } + } else { + setState(() { + _isLoading = false; + }); + ToastHelper.showErrorToast(context, 'Something went wrong'); + throw Exception('Failed to verify mobile number'); + } + } + } catch (e) { + setState(() { + _isLoading = false; + }); + ToastHelper.showErrorToast(context, 'Something went wrong'); + print('Error: $e'); + } + } + + Future resetYourPassword() async { + final password = resetPasswordController.text.trim(); + final confirmPassword = confirmPasswordController.text.trim(); + try { + if (confirmPassword != password) { + ToastHelper.showErrorToast(context, 'Passwords do not match'); + return; + } + setState(() { + _isLoading = true; + }); + + // Determine the API and the payload based on the visible field + String apiEndpoint = Environment.apiUrlEnrollment + 'savePassword'; + Map payload = { + 'email_id': emailController.text, + 'password': password, + 'confirm_password': confirmPassword + }; + + // var enteredMobileNumber = mobileController.text; + final response = await http.post( + Uri.parse(apiEndpoint), + body: json.encode(payload), + headers: { + HttpHeaders.contentTypeHeader: 'application/json', + }, + ); + + if (response.statusCode == 200) { + Map data = json.decode(response.body); + bool userVerification = data['data']['user_verification']; + String message = data['data']['message']; + if (userVerification) { + ToastHelper.showSuccessToast(context, message!); + + setState(() { + _isLoading = false; + resetPasswordEnable = false; + clickedForgotPassword = false; + otpFieldShow = false; + }); + // ToastHelper.showSuccessToast(context, message); + } else { + setState(() { + resetPasswordEnable = true; + _isLoading = false; + }); + ToastHelper.showErrorToast(context, message!); + print('Invalid mobile number'); + } + } else { + setState(() { + _isLoading = false; + }); + ToastHelper.showErrorToast(context, 'Something went wrong'); + throw Exception('Failed to verify mobile number'); + } + } catch (e) { + setState(() { + _isLoading = false; + }); + // ToastHelper.showErrorToast(context, 'Something went wrong'); + print('Error: $e'); + } + + // 🔹 Password validation + // if (password.isEmpty) { + // ToastHelper.showErrorToast(context, 'Please enter your password'); + // return; + // } + // if (password.length < 6) { + // ToastHelper.showErrorToast(context, 'Password must be at least 6 characters'); + // return; + // } + // if (!RegExp(r'^(?=.*[A-Z])(?=.*[0-9]).{6,}$').hasMatch(password)) { + // ToastHelper.showErrorToast(context, 'Include at least 1 uppercase letter and 1 number'); + // return; + // } + // + // // 🔹 Confirm password validation + // if (confirmPassword.isEmpty) { + // ToastHelper.showErrorToast(context, 'Please confirm your password'); + // return; + // } + } + + Future resendOTP() async { + try { + if (emailController.text.isNotEmpty) { + setState(() { + otpFieldShow = true; + _isLoading = true; + }); + + // Determine the API and the payload based on the visible field + String apiEndpoint = + Environment.apiUrlEnrollment + 'verifyEmployeeEmailId'; + + Map payload = {'email': emailController.text}; + + // var enteredMobileNumber = mobileController.text; + final response = await http.post( + Uri.parse(apiEndpoint), + body: json.encode(payload), + headers: { + HttpHeaders.contentTypeHeader: 'application/json', + }, + ); + + if (response.statusCode == 200) { + Map data = json.decode(response.body); + bool userVerification = data['data']['user_verification']; + String message = data['data']['message']; + if (userVerification) { + final SharedPreferences prefs = + await SharedPreferences.getInstance(); + // var enteredMobileNumber = mobileController.text; + // prefs.setString('empMobileNo', enteredMobileNumber); + ToastHelper.showSuccessToast( + context, 'OTP send to registered email'); + + setState(() { + otpFieldShow = true; + _isLoading = false; + }); + // ToastHelper.showSuccessToast(context, message); + } else { + setState(() { + _isLoading = false; + }); + ToastHelper.showErrorToast(context, message); + print('Invalid mobile number'); + } + } else { + setState(() { + _isLoading = false; + }); + ToastHelper.showErrorToast(context, 'Something went wrong'); + throw Exception('Failed to verify mobile number'); + } + } + } catch (e) { + setState(() { + _isLoading = false; + }); + ToastHelper.showErrorToast(context, 'Something went wrong'); + print('Error: $e'); + } + + } + + String getLoginDescription() { + if (switcherStatus == 1) { + return "Login with your email / mobile number and OTP to review and enroll for exciting health benefits for you and your family"; + } + + if (clickedForgotPassword) { + return "Enter your registered email to receive a OTP"; + } + + if (switcherStatus == 0 && resetPasswordEnable) { + return "Set your new password to continue accessing your Nhance account securely"; + } + + return "Login with your Username and Password to review and enroll for exciting health benefits for you and your family"; + } + + String getLoginHeading() { + if (switcherStatus == 1) { + return "Welcome to Nhance"; + } + + if (clickedForgotPassword) { + return "Forgot Password"; + } + + if (switcherStatus == 0 && resetPasswordEnable) { + return "Reset Password"; + } + + return "Welcome to Nhance"; + } + + + //Ends Login with UserName and Password + @override Widget build(BuildContext context) { Size _size = MediaQuery.of(context).size; EdgeInsets marginInsets = EdgeInsets.zero; + // const focusedBorderColor = Colors.white; + // const fillColor = Color.fromRGBO(243, 246, 249, 0); + // const borderColor = Color.fromRGBO(23, 171, 144, 0.4); + if (Responsive.isDesktop(context)) { marginInsets = const EdgeInsets.only( left: 0, @@ -409,657 +1016,1294 @@ class _loginState extends State { }, child: Scaffold( body: SingleChildScrollView( - keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, - child: Container( - height: _size.height, - color: Colors.white, - child: Stack( - children: [ - Visibility( - visible: _size.width <= 1100, - child: ClipRRect( - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(30), - bottomRight: Radius.circular(30), - ), - child: Container( - height: _size.height / 3, - width: double.infinity, - color: Color(0xFFFFFCE5), - child: Stack( - children: [ - Column( + keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, + child: Container( + height: _size.height, + color: Colors.white, + child: Stack( + children: [ + Visibility( + visible: _size.width <= 1100, + child: ClipRRect( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(30), + bottomRight: Radius.circular(30), + ), + child: Container( + height: _size.height / 3, + width: double.infinity, + color: Color(0xFFFFFCE5), + child: Stack( children: [ - SizedBox(height: _size.height / 6.4), - Row( - mainAxisAlignment: MainAxisAlignment - .center, // Align to the center + Column( children: [ - Expanded( - flex: Responsive.isDesktop(context) - ? 10 - : 12, - child: Align( - alignment: + SizedBox(height: _size.height / 6.4), + Row( + mainAxisAlignment: MainAxisAlignment + .center, // Align to the center + children: [ + Expanded( + flex: Responsive.isDesktop(context) + ? 10 + : 12, + child: Align( + alignment: Responsive.isDesktop(context) ? Alignment.centerLeft : Alignment.bottomCenter, - child: Image.asset( - 'assets/nhance_app_logo.png', - width: 150, - height: 100, - )), - ), - if (!Responsive.isMobile(context) && - !Responsive.isTablet(context)) - Expanded( - flex: 2, - child: MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: () { - // Add your navigation logic here - // For example, you can use Navigator.push to navigate to another page - Navigator.pushNamed( - context, 'hrLogin'); - }, - child: Row( - mainAxisAlignment: MainAxisAlignment - .end, // Align to the end (right) - children: [ - Text( - 'HR Login', - style: TextStyle( - color: Color( - 0xFF000000), // Text color - // Add other text styles as needed - ), + child: Image.asset( + 'assets/nhance_app_logo.png', + width: 150, + height: 100, + )), + ), + if (!Responsive.isMobile(context) && + !Responsive.isTablet(context)) + Expanded( + flex: 2, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () { + // Add your navigation logic here + // For example, you can use Navigator.push to navigate to another page + Navigator.pushNamed( + context, 'hrLogin'); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment + .end, // Align to the end (right) + children: [ + Text( + 'HR Login', + style: TextStyle( + color: Color( + 0xFF000000), // Text color + // Add other text styles as needed + ), + ), + SizedBox(width: 5), + Icon( + Icons + .east, // Icon for customer login + color: Colors + .black, // Adjust color as needed + ), + ], ), - SizedBox(width: 5), - Icon( - Icons - .east, // Icon for customer login - color: Colors - .black, // Adjust color as needed - ), - ], + ), ), ), - ), - ), + ], + ), ], ), ], ), - ], + ), ), ), - ), - ), - Container( - margin: marginInsets, - alignment: Alignment.bottomCenter, - child: SingleChildScrollView( - child: Form( - key: _formKey, - child: Column( - children: [ - Row( + Container( + margin: marginInsets, + alignment: Alignment.bottomCenter, + child: SingleChildScrollView( + child: Form( + key: _formKey, + child: Column( children: [ - Expanded( - flex: _size.width < 1100 ? 6 : 12, - child: Container( - margin: _size.width > 1100 - ? EdgeInsets.only(left: 20, right: 20) - : EdgeInsets.only(left: 0, right: 0), - child: Column( - mainAxisAlignment: + Row( + children: [ + Expanded( + flex: _size.width < 1100 ? 6 : 12, + child: Container( + margin: _size.width > 1100 + ? EdgeInsets.only(left: 20, right: 20) + : EdgeInsets.only(left: 0, right: 0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (!Responsive.isMobile(context) && - !Responsive.isTablet(context)) - Row( - children: [ - Expanded( - flex: 12, - child: Align( - alignment: Responsive - .isDesktop(context) - ? Alignment.centerLeft - : Alignment - .bottomCenter, // Align to the start - child: _size.width <= 1100 - ? Image.asset( - 'assets/nhance_app_logo.png', - width: 150, - height: 150, - ) - : _size.width > 1100 - ? Image.asset( - 'assets/nhance_app_logo.png', - width: 150, - height: 150, - ) - : Image.asset( - 'assets/nhance_app_logo.png', - width: 150, - height: 150, - ), - )), - ], - ), - // SizedBox( - // height: Responsive.isDesktop(context) - // ? _size.height * 0.1 - // : 10, - // ), - SizedBox(height: 10), - Container( - margin: Responsive.isDesktop(context) - ? EdgeInsets.symmetric( - horizontal: 150) - : EdgeInsets.symmetric( - horizontal: 0), - child: Row( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Text( - "Welcome to Nhance", - style: GoogleFonts.poppins( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - SizedBox( - height: 10, - ), - Container( - margin: Responsive.isDesktop(context) - ? EdgeInsets.symmetric( - horizontal: 150) - : EdgeInsets.symmetric( - horizontal: 0), - child: Row( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Expanded( - child: Text( - "Login with your ${isEmailFieldVisible ? 'email' : 'mobile number'} and OTP to review and enroll for exciting health benefits for you and your family", - style: GoogleFonts.poppins( - fontSize: 12, - color: Color(0xFF000000)), - textAlign: TextAlign.center, - ), - ) - ], - ), - ), - SizedBox( - height: 20, - ), - Column( children: [ - isEmailFieldVisible - ? Container( - height: 55, - margin: Responsive - .isDesktop(context) - ? EdgeInsets.symmetric( - horizontal: 150) - : EdgeInsets.symmetric( - horizontal: 0), - decoration: BoxDecoration( - border: Border.all( - width: 1, - color: Colors.grey), - borderRadius: - BorderRadius.circular( - 10), - ), - child: TextFormField( - controller: - emailController, - keyboardType: - TextInputType - .emailAddress, - decoration: - InputDecoration( - border: - InputBorder.none, - hintText: - "Enter your email", - contentPadding: - EdgeInsets - .symmetric( - horizontal: - 10), + if (!Responsive.isMobile(context) && + !Responsive.isTablet(context)) + Row( + children: [ + Expanded( + flex: 12, + child: Align( + alignment: Alignment + .topLeft, // ✅ Always top-left + child: _size.width <= 1100 + ? Image.asset( + 'assets/nhance_app_logo.png', + width: 150, + height: 150, + ) + : _size.width > 1100 + ? Image.asset( + 'assets/nhance_app_logo.png', + width: 150, + height: 150, + ) + : Image.asset( + 'assets/nhance_app_logo.png', + width: 150, + height: 150, ), - validator: (value) { - if (value == null || - value.isEmpty) { - return 'Please enter your email'; - } - if (!RegExp( - r'^[^@]+@[^@]+\.[^@]+') - .hasMatch(value)) { - return 'Please enter a valid email'; - } - return null; - }, ), - ) - : Container( - height: 55, - margin: Responsive - .isDesktop(context) - ? EdgeInsets.symmetric( - horizontal: 150) - : EdgeInsets.symmetric( - horizontal: 0), + ), + ], + ), + SizedBox( + height: Responsive.isDesktop(context) + ? _size.height * 0.1 + : 10, + ), + SizedBox(height: 10), + if (Responsive.isDesktop(context)) + Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Container( + padding: + const EdgeInsets.all(3), decoration: BoxDecoration( - border: Border.all( - width: 1, - color: Colors.grey), + color: Colors.grey.shade100, borderRadius: - BorderRadius.circular( - 10), + BorderRadius.circular(50), ), child: Row( children: [ - SizedBox(width: 10), - SizedBox( - width: 40, - // child: TextField( - // controller: - // countryController, - // keyboardType: - // TextInputType - // .number, - // decoration: - // InputDecoration( - // border: - // InputBorder - // .none, - // ), - // ), - child: TextFormField( - controller: - countryController, - keyboardType: - TextInputType - .number, - decoration: - InputDecoration( - border: - InputBorder - .none, - ), - validator: (value) { - if (value == - null || - value - .isEmpty) { - return 'Required'; - } - - return null; - }, - inputFormatters: [ - FilteringTextInputFormatter - .allow(RegExp( - r'[0-9+]')), - LengthLimitingTextInputFormatter( - 3), - ], - ), - ), - Text( - "|", - style: TextStyle( - fontSize: 33, - color: - Colors.grey), - ), - SizedBox(width: 10), - Expanded( - child: TextFormField( - controller: - mobileController, - keyboardType: - TextInputType - .phone, - decoration: - InputDecoration( - border: - InputBorder - .none, - hintText: - "Enter your mobile number", - ), - validator: (value) { - if (value == - null || - value - .isEmpty) { - return 'Please enter your mobile number'; - } - if (value - .length != - 10) { - return 'Mobile number must be 10 digits'; - } - return null; - }, - inputFormatters: [ - FilteringTextInputFormatter - .digitsOnly, - LengthLimitingTextInputFormatter( - 10), - ], - ), - ), + buildTab( + "Login with OTP", 0), + const SizedBox(width: 10), + buildTab( + "Login with Password", + 1), ], ), ), - SizedBox(height: 15), + ], + ), + SizedBox(height: 10), Container( - margin: - Responsive.isDesktop(context) - ? EdgeInsets.symmetric( - horizontal: 150) - : EdgeInsets.symmetric( - horizontal: 0), - child: SizedBox( - width: double.infinity, - height: 45, - child: ElevatedButton( - style: - ElevatedButton.styleFrom( - backgroundColor: - Color(0xFF00989E), - shape: - RoundedRectangleBorder( - borderRadius: - BorderRadius.circular( - 10), + margin: Responsive.isDesktop(context) + ? EdgeInsets.symmetric( + horizontal: 150) + : EdgeInsets.symmetric( + horizontal: 0), + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Text( + getLoginHeading(), + style: GoogleFonts.poppins( + fontSize: 16, + fontWeight: FontWeight.bold, ), ), - onPressed: _isLoading - ? null - : verifyMobileAndEmailNumber, - child: _isLoading - ? CircularProgressIndicator( + ], + ), + ), + SizedBox( + height: 10, + ), + Container( + margin: Responsive.isDesktop(context) + ? EdgeInsets.symmetric( + horizontal: 150) + : EdgeInsets.symmetric( + horizontal: 0), + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Expanded( + child: Text( + getLoginDescription(), + style: GoogleFonts.poppins( + fontSize: 12, + color: Color(0xFF000000)), + textAlign: TextAlign.center, + ), + ) + ], + ), + ), + SizedBox( + height: 20, + ), + if (switcherStatus == 1) ...[ + Column( + children: [ + Container( + height: 55, + margin: Responsive.isDesktop(context) + ? const EdgeInsets.symmetric(horizontal: 150) + : const EdgeInsets.symmetric(horizontal: 0), + decoration: BoxDecoration( + border: Border.all(width: 1, color: Colors.grey), + borderRadius: BorderRadius.circular(10), + ), + child: TextFormField( + controller: emailMobileController, + keyboardType: TextInputType.text, + decoration: const InputDecoration( + border: InputBorder.none, + hintText: "Email / Mobile Number ", + contentPadding: EdgeInsets.symmetric(horizontal: 10), + ), + validator: (value) { + if (value == null || value.trim().isEmpty) { + return "Please enter email or mobile number"; + } + + String input = value.trim(); + + // ❌ Reject all spaces + if (input.contains(' ')) { + return "No spaces allowed"; + } + + final emailRegex = RegExp(r'^[^@]+@[^@]+\.[^@]+$'); + final mobileRegex = RegExp(r'^[0-9]{10}$'); + + bool isEmailFormat = emailRegex.hasMatch(input); + bool isMobileFormat = mobileRegex.hasMatch(input); + + // --------------------------- + // 🛑 MOBILE VALIDATION + // --------------------------- + if (RegExp(r'^[0-9]+$').hasMatch(input)) { + if (input.length != 10) { + return "Mobile number must be exactly 10 digits"; + } + } + + // --------------------------- + // 🛑 EMAIL VALIDATION + // --------------------------- + + // Reject anything that has '@' but is NOT a valid email format + if (input.contains('@') && !isEmailFormat) { + return "Enter a valid email address"; + } + + // Reject email with extra digits at the end + if (input.contains('@') && RegExp(r'\d+$').hasMatch(input)) { + return "Email cannot contain extra numbers"; + } + + // Reject email+mobile combination + if (input.contains('@') && RegExp(r'\d{10}$').hasMatch(input)) { + return "Enter only email OR mobile number"; + } + + // --------------------------- + // 🛑 MIXED CONTENT (letters + digits but NOT email) + // --------------------------- + bool hasLetters = RegExp(r'[A-Za-z]').hasMatch(input); + bool hasDigits = RegExp(r'[0-9]').hasMatch(input); + + if ((hasLetters && hasDigits) && !input.contains('@')) { + return "Enter only email OR 10-digit mobile number"; + } + + // --------------------------- + // 🟢 FINAL CHECK + // --------------------------- + if (!isEmailFormat && !isMobileFormat) { + return "Enter a valid email or 10-digit mobile number"; + } + + return null; + } + + ), + ), + SizedBox(height: 15), + Container( + margin: Responsive.isDesktop( + context) + ? EdgeInsets.symmetric( + horizontal: 150) + : EdgeInsets.symmetric( + horizontal: 0), + child: SizedBox( + width: double.infinity, + height: 45, + child: ElevatedButton( + style: ElevatedButton + .styleFrom( + backgroundColor: + Color(0xFF00989E), + shape: + RoundedRectangleBorder( + borderRadius: + BorderRadius + .circular(10), + ), + ), + onPressed: _isLoading + ? null + : verifyMobileAndEmailNumber, + child: _isLoading + ? CircularProgressIndicator( valueColor: - AlwaysStoppedAnimation< - Color>( + AlwaysStoppedAnimation< + Color>( Color(0xFF00989E), ), ) - : Text( - isEmailFieldVisible - ? "Login with Email OTP" - : "Login with Mobile OTP", + : Text( "Login with Email / Mobile OTP", style: GoogleFonts .poppins( color: Color( 0xFFFFFFFF), ), ), - ), - ), - ), - SizedBox(height: 15), - MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: toggleField, - child: Text( - isEmailFieldVisible - ? "Login with Mobile No" - : "Login with Email", - style: TextStyle( - color: Color(0xFF00989E), + ), + ), ), + SizedBox(height: 15), + // MouseRegion( + // cursor: + // SystemMouseCursors.click, + // child: GestureDetector( + // onTap: toggleField, + // child: Text( + // isEmailFieldVisible + // ? "Login with Mobile No" + // : "Login with Email", + // style: TextStyle( + // color: Color(0xFF00989E), + // ), + // ), + // ), + // ), + ], + ), + ] else if (switcherStatus == 0) ...[ + if (!resetPasswordEnable) + Column( + children: [ + if (!clickedForgotPassword) ...[ + Container( + height: 55, + margin: Responsive + .isDesktop(context) + ? EdgeInsets.symmetric( + horizontal: 150) + : EdgeInsets.symmetric( + horizontal: 0), + decoration: BoxDecoration( + border: Border.all( + width: 1, + color: Colors.grey), + borderRadius: + BorderRadius.circular( + 10), + ), + child: TextFormField( + controller: + emailController, + keyboardType: + TextInputType + .emailAddress, + decoration: + InputDecoration( + border: + InputBorder.none, + hintText: + "Enter your email", + contentPadding: + EdgeInsets + .symmetric( + horizontal: + 10), + ), + validator: (value) { + if (value == null || + value.isEmpty) { + return 'Please enter your email'; + } + if (!RegExp( + r'^[^@]+@[^@]+\.[^@]+') + .hasMatch(value)) { + return 'Please enter a valid email'; + } + return null; + }, + ), + ), + SizedBox(height: 10), + Container( + height: 55, + margin: Responsive + .isDesktop(context) + ? EdgeInsets.symmetric( + horizontal: 150) + : EdgeInsets.symmetric( + horizontal: 0), + decoration: BoxDecoration( + border: Border.all( + width: 1, + color: Colors.grey), + borderRadius: + BorderRadius.circular( + 10), + ), + child: TextFormField( + controller: + passwordController, + obscureText: + _obscurePassword, + textAlignVertical: + TextAlignVertical + .center, // ✅ THE FIX + decoration: + InputDecoration( + border: + InputBorder.none, + hintText: + "Enter your password", + contentPadding: + EdgeInsets + .symmetric( + horizontal: 10, + ), + suffixIcon: IconButton( + icon: Icon( + _obscurePassword + ? Icons + .visibility_off + : Icons + .visibility, + color: Colors.grey, + ), + onPressed: () { + setState(() { + _obscurePassword = + !_obscurePassword; + }); + }, + ), + ), + validator: (value) { + if (value == null || + value.isEmpty) { + return 'Please enter your password'; + } + if (value.length < 6) { + return 'Password must be at least 6 characters'; + } + // Optional strong password rule + if (!RegExp( + r'^(?=.*[A-Z])(?=.*[0-9]).{6,}$') + .hasMatch(value)) { + return 'Include at least 1 uppercase letter and 1 number'; + } + return null; + }, + ), + ), + SizedBox(height: 15), + Container( + margin: Responsive + .isDesktop(context) + ? EdgeInsets.symmetric( + horizontal: 150) + : EdgeInsets.symmetric( + horizontal: 0), + child: Row( + mainAxisAlignment: + MainAxisAlignment + .end, // Align text to the right + children: [ + InkWell( + onTap: () { + forgotPassword(); + }, + child: Text( + "Forgot Password?", + style: GoogleFonts + .poppins( + color: Colors + .blue), + ), + ), + ], + ), + ), + SizedBox(height: 15), + Container( + margin: Responsive + .isDesktop(context) + ? EdgeInsets.symmetric( + horizontal: 150) + : EdgeInsets.symmetric( + horizontal: 0), + child: SizedBox( + width: double.infinity, + height: 40, + child: ElevatedButton( + style: ElevatedButton + .styleFrom( + backgroundColor: + Color(0xFF00989E), + shape: + RoundedRectangleBorder( + borderRadius: + BorderRadius + .circular( + 10), + ), + ), + onPressed: _isLoading + ? null + : loginWithUsernameAndPw, + child: _isLoading + ? CircularProgressIndicator( + valueColor: + AlwaysStoppedAnimation< + Color>( + Color( + 0xFF00989E), + ), + ) + : Text( + "Login", + style: + GoogleFonts + .poppins( + color: Color( + 0xFFFFFFFF), + ), + ), + ), + ), + ), + ], + if (clickedForgotPassword) ...[ + Container( + height: 55, + margin: Responsive + .isDesktop(context) + ? EdgeInsets.symmetric( + horizontal: 150) + : EdgeInsets.symmetric( + horizontal: 0), + decoration: BoxDecoration( + border: Border.all( + width: 1, + color: Colors.grey), + borderRadius: + BorderRadius.circular( + 10), + ), + child: TextFormField( + controller: + emailController, + keyboardType: + TextInputType + .emailAddress, + decoration: + InputDecoration( + border: + InputBorder.none, + hintText: + "Enter your email", + contentPadding: + EdgeInsets + .symmetric( + horizontal: + 10), + ), + readOnly: otpFieldShow, + validator: (value) { + if (value == null || + value.isEmpty) { + return 'Please enter your email'; + } + if (!RegExp( + r'^[^@]+@[^@]+\.[^@]+') + .hasMatch(value)) { + return 'Please enter a valid email'; + } + return null; + }, + ), + ), + if (otpFieldShow) ...[ + SizedBox(height: 10), + AnimatedSwitcher( + duration: const Duration( + milliseconds: 500, + ), // animation speed + switchInCurve: + Curves.easeInOutCirc, + switchOutCurve: + Curves.easeOutCirc, + child: + clickedForgotPassword + ? Column( + key: const ValueKey( + 'otp_block'), + crossAxisAlignment: + CrossAxisAlignment + .start, + children: [ + Container( + child: Center( + child: + Text( + 'OTP', + style: + TextStyle( + fontSize: Responsive.isMobile(context) + ? 14 + : 18, + fontWeight: + FontWeight.w600, + color: + Colors.black, + ), + ), + ), + ), + const SizedBox( + height: + 10), + Container( + alignment: Alignment.center, + margin: Responsive.isDesktop(context) + ? EdgeInsets.symmetric( + horizontal: 150) + : EdgeInsets.symmetric( + horizontal: 0), + child: Pinput( + length: 6, + // defaultPinTheme: defaultPinTheme, + // focusedPinTheme: focusedPinTheme, + // submittedPinTheme: submittedPinTheme, + inputFormatters: [ + FilteringTextInputFormatter + .digitsOnly, // ✅ allows only 0–9 + ], + keyboardType: TextInputType.number, + showCursor: true, + controller: _otpController, + validator: + (value) { + if (value == null || + value.isEmpty) { + return 'Please enter OTP'; + } + if (value.length < + 6) { + return 'OTP must be 6 digits'; + } + // if (otpValueStatus) { + // // example + // return 'Invalid OTP'; + // } + return null; + }, + ), + ), + const SizedBox( + height: + 10), + Container( + margin: Responsive.isDesktop(context) + ? EdgeInsets.symmetric( + horizontal: + 150) + : EdgeInsets.symmetric( + horizontal: + 0), + alignment: + Alignment + .centerRight, // center the text + child:InkWell( + onTap: (){ + setState(() { + otpFieldShow = false; + }); + print('ABCDEF'); + resendOTP(); + }, + mouseCursor: SystemMouseCursors.click, + child: Text( + 'Didn’t Receive Code?', + style: GoogleFonts.poppins( + color: Colors.blue, + fontSize: 14, + ), + ), + ), + // RichText( + // textAlign: + // TextAlign.right, + // text: + // TextSpan( + // text: + // 'Didn’t Receive Code? ', // normal text + // style: + // TextStyle( + // fontSize: Responsive.isMobile(context) + // ? 12 + // : 14, + // fontWeight: + // FontWeight.normal, + // color: + // Colors.black, + // ), + // children: [ + // TextSpan( + // text: 'Resend', // bold clickable part + // style: TextStyle( + // fontWeight: FontWeight.bold, + // color: Colors.white, + // decoration: TextDecoration.underline, // optional + // ), + // // recognizer: TapGestureRecognizer() + // // ..onTap = () { + // // + // // }, + // ), + // ], + // ), + // ), + ), + ], + ) + : const SizedBox + .shrink(), // Empty widget when false + ), + ], + SizedBox(height: 20), + Container( + margin: Responsive + .isDesktop(context) + ? EdgeInsets.symmetric( + horizontal: 150) + : EdgeInsets.symmetric( + horizontal: 0), + child: SizedBox( + width: double.infinity, + height: 40, + child: ElevatedButton( + style: ElevatedButton + .styleFrom( + backgroundColor: + Color(0xFF00989E), + shape: + RoundedRectangleBorder( + borderRadius: + BorderRadius + .circular( + 10), + ), + ), + onPressed: _isLoading + ? null + : () => { + otpFieldShow + ? otpVerify() + : mailVerify() + }, + child: _isLoading + ? CircularProgressIndicator( + valueColor: + AlwaysStoppedAnimation< + Color>( + Color( + 0xFF00989E), + ), + ) + : Text( + otpFieldShow + ? "Verify OTP " + : "Verify Mail", + style: + GoogleFonts + .poppins( + color: Color( + 0xFFFFFFFF), + ), + ), + ), + ), + ), + ], + ], + ), + if (resetPasswordEnable) ...[ + Column( + children: [ + Container( + height: 55, + margin: Responsive.isDesktop(context) + ? const EdgeInsets.symmetric(horizontal: 150) + : const EdgeInsets.symmetric(horizontal: 0), + decoration: BoxDecoration( + border: Border.all(width: 1, color: Colors.grey), + borderRadius: BorderRadius.circular(10), + ), + child: TextFormField( + controller: resetPasswordController, + obscureText: _resetObscurePassword, + onChanged: validatePassword, + textAlignVertical: TextAlignVertical.center, + decoration: InputDecoration( + border: InputBorder.none, + hintText: "New Password", + contentPadding: const EdgeInsets.symmetric(horizontal: 10), + suffixIcon: IconButton( + icon: Icon( + _resetObscurePassword ? Icons.visibility_off : Icons.visibility, + color: Colors.grey, + ), + onPressed: () { + setState(() { + _resetObscurePassword = !_resetObscurePassword; + }); + }, + ), + ), + ), + ), + + const SizedBox(height: 8), + + // 🔹 VALIDATION LIST + Padding( + padding: Responsive.isDesktop(context) + ? const EdgeInsets.symmetric(horizontal: 150) + : const EdgeInsets.symmetric(horizontal: 5), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded(child: _buildCheckItem(hasMinLength, "Minimum 8 characters")), + Expanded(child: _buildCheckItem(hasSpecialChar, "1 special character")), + ], + ), + Row( + children: [ + Expanded(child: _buildCheckItem(hasUpperLower, "1 UPPER or lower case")), + Expanded(child: _buildCheckItem(hasNumber, "1 numerical")), + ], + ), + ], + ), + ), + const SizedBox(height: 10), + Container( + height: 55, + margin: Responsive.isDesktop(context) + ? const EdgeInsets.symmetric(horizontal: 150) + : const EdgeInsets.symmetric(horizontal: 0), + decoration: BoxDecoration( + border: Border.all(width: 1, color: Colors.grey), + borderRadius: BorderRadius.circular(10), + ), + child: TextFormField( + controller: confirmPasswordController, + obscureText: _obscureConfirmPassword, + textAlignVertical: TextAlignVertical.center, + decoration: InputDecoration( + border: InputBorder.none, + hintText: "Confirm Password", + contentPadding: const EdgeInsets.symmetric(horizontal: 10), + suffixIcon: IconButton( + icon: Icon( + _obscureConfirmPassword + ? Icons.visibility_off + : Icons.visibility, + color: Colors.grey, + ), + onPressed: () { + setState(() { + _obscureConfirmPassword = !_obscureConfirmPassword; + }); + }, + ), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please confirm your password'; + } + if (value != resetPasswordController.text) { + return 'Passwords do not match'; + } + return null; + }, + ), + ), + ], + ), + SizedBox(height: 15), + Container( + margin: + Responsive.isDesktop(context) + ? EdgeInsets.symmetric( + horizontal: 150) + : EdgeInsets.symmetric( + horizontal: 0), + child: SizedBox( + width: double.infinity, + height: 40, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: isPasswordValid ? Color(0xFF00989E) : Colors.grey.shade400, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + onPressed: _isLoading || !isPasswordValid + ? null + : () { + resetYourPassword(); + }, + child: _isLoading + ? CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(Colors.white), + ) + : Text( + "Reset Password", + style: GoogleFonts.poppins(color: Colors.white), + ), + ), + ), + ), + ] + ], + SizedBox( + height: _size.width <= 1100 ? 0 : 0, + ), + // _size.width > 1100 + // ? Container( + // margin: EdgeInsets.symmetric( + // horizontal: 150), + // child: Column( + // children: [ + // SizedBox(height: 30), + // Text( + // "Benefits of Login", + // style: + // GoogleFonts.poppins( + // fontSize: 20, + // fontWeight: + // FontWeight.bold, + // ), + // ), + // SizedBox(height: 15), + // ], + // )) + // : SizedBox(), + // _size.width > 1100 + // ? Container( + // margin: EdgeInsets.symmetric( + // horizontal: 150), + // child: Row( + // mainAxisAlignment: + // MainAxisAlignment.center, + // children: [ + // Expanded( + // flex: 6, + // child: Container( + // padding: EdgeInsets + // .symmetric( + // vertical: 8), + // child: Row( + // mainAxisAlignment: + // MainAxisAlignment + // .center, + // children: [ + // Expanded( + // child: Container( + // padding: EdgeInsets + // .symmetric( + // vertical: + // 12), + // decoration: + // BoxDecoration( + // border: + // Border( + // right: + // BorderSide( + // width: 1, + // color: Colors + // .black, + // ), + // ), + // ), + // child: Column( + // children: [ + // Icon( + // Icons + // .policy, + // color: Color( + // 0xFFE26728)), + // SizedBox( + // height: + // 10), + // Text( + // "View Policy"), + // ], + // ), + // ), + // ), + // Expanded( + // child: Container( + // padding: EdgeInsets + // .symmetric( + // vertical: + // 12), + // child: Column( + // children: [ + // Icon( + // Icons + // .edit, + // color: Color( + // 0xFFE26728)), + // SizedBox( + // height: + // 10), + // Text( + // "Manage Claims"), + // ], + // ), + // ), + // ), + // ], + // ), + // ), + // ), + // ], + // ), + // ) + // : SizedBox( + // height: Responsive.isDesktop( + // context) + // ? _size.height * 0.1 + // : _size.height * 0.2, + // ), + SizedBox( + height: Responsive.isDesktop(context) + ? _size.height * 0.3 + : _size.height * 0.2, + ), + // SizedBox( + // height: _size.height * 0.1, + // ), + Container( + alignment: Alignment.bottomCenter, + padding: + EdgeInsets.symmetric(vertical: 8), + child: RichText( + textAlign: TextAlign.center, + text: TextSpan( + text: + 'By continuing, you agree with our ', + style: GoogleFonts.poppins( + color: Colors.black, + fontSize: 9, + ), + children: [ + WidgetSpan( + child: MouseRegion( + cursor: SystemMouseCursors + .click, + child: GestureDetector( + onTap: () { + context.go( + '/privacypolicy'); + // Navigator.pushNamed( + // context, + // 'privacypolicy'); + }, + child: Text( + 'privacy policy ', + style: + GoogleFonts.poppins( + color: + Color(0xFF00989E), + fontSize: 9, + decoration: + TextDecoration + .underline, + ), + ), + ), + ), + ), + TextSpan( + text: 'and ', + style: GoogleFonts.poppins( + color: Colors.black, + fontSize: 9, + ), + ), + WidgetSpan( + child: MouseRegion( + cursor: SystemMouseCursors + .click, + child: GestureDetector( + onTap: () { + context + .go('/termsofuse'); + // Navigator.pushNamed( + // context, + // 'termsofuse'); + }, + child: Text( + 'terms of use', + style: + GoogleFonts.poppins( + color: + Color(0xFF00989E), + fontSize: 9, + decoration: + TextDecoration + .underline, + ), + ), + ), + ), + ), + ], ), ), ), ], ), - SizedBox( - height: _size.width <= 1100 ? 0 : 0, - ), - // _size.width > 1100 - // ? Container( - // margin: EdgeInsets.symmetric( - // horizontal: 150), - // child: Column( - // children: [ - // SizedBox(height: 30), - // Text( - // "Benefits of Login", - // style: - // GoogleFonts.poppins( - // fontSize: 20, - // fontWeight: - // FontWeight.bold, - // ), - // ), - // SizedBox(height: 15), - // ], - // )) - // : SizedBox(), - // _size.width > 1100 - // ? Container( - // margin: EdgeInsets.symmetric( - // horizontal: 150), - // child: Row( - // mainAxisAlignment: - // MainAxisAlignment.center, - // children: [ - // Expanded( - // flex: 6, - // child: Container( - // padding: EdgeInsets - // .symmetric( - // vertical: 8), - // child: Row( - // mainAxisAlignment: - // MainAxisAlignment - // .center, - // children: [ - // Expanded( - // child: Container( - // padding: EdgeInsets - // .symmetric( - // vertical: - // 12), - // decoration: - // BoxDecoration( - // border: - // Border( - // right: - // BorderSide( - // width: 1, - // color: Colors - // .black, - // ), - // ), - // ), - // child: Column( - // children: [ - // Icon( - // Icons - // .policy, - // color: Color( - // 0xFFE26728)), - // SizedBox( - // height: - // 10), - // Text( - // "View Policy"), - // ], - // ), - // ), - // ), - // Expanded( - // child: Container( - // padding: EdgeInsets - // .symmetric( - // vertical: - // 12), - // child: Column( - // children: [ - // Icon( - // Icons - // .edit, - // color: Color( - // 0xFFE26728)), - // SizedBox( - // height: - // 10), - // Text( - // "Manage Claims"), - // ], - // ), - // ), - // ), - // ], - // ), - // ), - // ), - // ], - // ), - // ) - // : SizedBox( - // height: Responsive.isDesktop( - // context) - // ? _size.height * 0.1 - // : _size.height * 0.2, - // ), - SizedBox( - height: Responsive.isDesktop(context) - ? _size.height * 0.3 - : _size.height * 0.2, - ), - - // SizedBox( - // height: _size.height * 0.1, - // ), - Container( - alignment: Alignment.bottomCenter, - padding: - EdgeInsets.symmetric(vertical: 8), - child: RichText( - textAlign: TextAlign.center, - text: TextSpan( - text: - 'By continuing, you agree with our ', - style: GoogleFonts.poppins( - color: Colors.black, - fontSize: 9, - ), - children: [ - WidgetSpan( - child: MouseRegion( - cursor: SystemMouseCursors - .click, - child: GestureDetector( - onTap: () { - context.go( - '/privacypolicy'); - // Navigator.pushNamed( - // context, - // 'privacypolicy'); - }, - child: Text( - 'privacy policy ', - style: - GoogleFonts.poppins( - color: - Color(0xFF00989E), - fontSize: 9, - decoration: - TextDecoration - .underline, - ), - ), - ), - ), - ), - TextSpan( - text: 'and ', - style: GoogleFonts.poppins( - color: Colors.black, - fontSize: 9, - ), - ), - WidgetSpan( - child: MouseRegion( - cursor: SystemMouseCursors - .click, - child: GestureDetector( - onTap: () { - context - .go('/termsofuse'); - // Navigator.pushNamed( - // context, - // 'termsofuse'); - }, - child: Text( - 'terms of use', - style: - GoogleFonts.poppins( - color: - Color(0xFF00989E), - fontSize: 9, - decoration: - TextDecoration - .underline, - ), - ), - ), - ), - ), - ], - ), - ), - ), - ], + ), ), - ), + if (_size.width > 1100) + Expanded( + flex: _size.width < 1100 ? 6 : 12, + child: LayoutBuilder( + builder: (BuildContext context, + BoxConstraints constraints) { + if (constraints.maxWidth > 600) { + return Image.asset( + 'assets/login_web.jpg', + height: _size.height, + fit: BoxFit.cover, + ); + } else { + return SizedBox(); + } + }, + ), + ), + ], ), - if (_size.width > 1100) - Expanded( - flex: _size.width < 1100 ? 6 : 12, - child: LayoutBuilder( - builder: (BuildContext context, - BoxConstraints constraints) { - if (constraints.maxWidth > 600) { - return Image.asset( - 'assets/login_web.jpg', - height: _size.height, - fit: BoxFit.cover, - ); - } else { - return SizedBox(); - } - }, - ), - ), ], ), - ], + ), ), ), - ), - ), - ], - )), - ))); + ], + )), + ))); } -} + + Widget _buildCheckItem(bool status, String text) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: Row( + children: [ + Icon( + status ? Icons.check : Icons.close, + color: status ? Colors.green : Colors.red, + size: 18, + ), + const SizedBox(width: 6), + Text( + text, + style: TextStyle( + color: status ? Colors.green : Colors.red, + fontSize: 14, + ), + ), + ], + ), + ); + } + + + Widget buildTab(String title, int index) { + final isSelected = selectedIndex == index + 1; + return GestureDetector( + onTap: () { + setState(() { + selectedIndex = index + 1; + if (selectedIndex == 2) { + switcherStatus = 0; + + clickedForgotPassword = false; + passwordController.text = ''; + passwordController.clear(); + resetPasswordController.text = ''; + resetPasswordController.clear(); + confirmPasswordController.text = ''; + confirmPasswordController.clear(); + _otpController.text = ''; + _otpController.clear(); + otpFieldShow = false; + otpValueStatus = false; + resetPasswordEnable = false; + emailController.text = ''; + emailController.clear(); + + _formKey.currentState?.reset(); + } else { + switcherStatus = 1; + emailMobileController.text = ''; + emailMobileController.clear(); + _formKey.currentState?.reset(); + } + }); + _formKey.currentState?.reset(); + }, + child: Container( + padding: EdgeInsets.only( + top: 6.5, + bottom: 6.0, + left: 40.0, + right: 40.0, + ), + decoration: BoxDecoration( + color: isSelected ? const Color(0xFF00989E) : Colors.transparent, + boxShadow: isSelected + ? [ + BoxShadow( + color: isSelected + ? const Color(0xFF00989E) + : Colors.transparent, // Grey shadow + spreadRadius: 0.2, + blurRadius: 1, + offset: const Offset(0, 1), // Horizontal, Vertical + ), + ] + : [], + borderRadius: BorderRadius.circular(16), + ), + child: Text( + title, + style: GoogleFonts.poppins( + fontSize: 12, + color: isSelected ? Colors.white : Colors.black, + fontWeight: FontWeight.w500, + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/pages/postEnrollment/claims.dart b/lib/pages/postEnrollment/claims.dart index 04b26cf..a5d0cfb 100755 --- a/lib/pages/postEnrollment/claims.dart +++ b/lib/pages/postEnrollment/claims.dart @@ -960,15 +960,25 @@ class _claimsState extends State { print(claimStatus); print(claimsDepartmentName); + + final Map statusColors = { - 'Received': Colors.blueGrey, // New / Received - 'Under Process': Colors.orangeAccent, // Work in progress - 'Approved': Colors.green, // Approved - 'Settled': Colors.teal, // Completed successfully - 'Returned': Colors.deepOrange, // Sent back for correction - 'Rejected': Colors.red, // Rejected - 'Cancelled': Colors.redAccent, // Cancelled - 'Closed': Colors.grey, // Closed + 'Claim Received': Colors.blueGrey, + 'Under Process' : Colors.orangeAccent, + 'Information Required': Colors.deepOrange, + 'Approved': Colors.green, + 'Settled': Colors.teal, + 'Denial Review Awaited':Colors.redAccent, + 'Rejected': Colors.red, + + // 'Received': Colors.blueGrey, // New / Received + // 'Under Process': Colors.orangeAccent, // Work in progress + // 'Approved': Colors.green, // Approved + // 'Settled': Colors.teal, // Completed successfully + // 'Returned': Colors.deepOrange, // Sent back for correction + // 'Rejected': Colors.red, // Rejected + // 'Cancelled': Colors.redAccent, // Cancelled + // 'Closed': Colors.grey, // Closed }; // Determine the color based on the claimStatus diff --git a/lib/pages/postEnrollment/claimshistory.dart b/lib/pages/postEnrollment/claimshistory.dart index 226ece7..4324445 100644 --- a/lib/pages/postEnrollment/claimshistory.dart +++ b/lib/pages/postEnrollment/claimshistory.dart @@ -601,6 +601,7 @@ class _ClaimHistoryPopupState extends State { Widget _getStepTitleFromApi(String status, Map data) { final modifiedBy = data['modified_by'] ?? ''; final modifiedAt = data['modified_at'] ?? ''; + final symbol = (data['modified_by'] != null && data['modified_by'] != '') ? ' - ' : ''; final isDesktop = Responsive.isDesktop(context); return RichText( @@ -615,7 +616,7 @@ class _ClaimHistoryPopupState extends State { ), ), TextSpan( - text: ' ($modifiedBy – $modifiedAt)', + text: ' ($modifiedBy$symbol$modifiedAt)', style: GoogleFonts.poppins( fontSize: Responsive.isDesktop(context) ? 14 : 11, fontWeight: FontWeight.w400, diff --git a/lib/pages/postEnrollment/home.dart b/lib/pages/postEnrollment/home.dart index b591ede..febddc8 100755 --- a/lib/pages/postEnrollment/home.dart +++ b/lib/pages/postEnrollment/home.dart @@ -1053,13 +1053,23 @@ class _HomeState extends State { crossAxisCount: Responsive.isDesktop(context) ? 2 : 1, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, - childAspectRatio: Responsive.isDesktop(context) ? 3.2 : 2.8), + childAspectRatio: Responsive.isDesktop(context) ? 2.2 : 2.6), itemCount: data.length, itemBuilder: (BuildContext context, int index) { var item = data[index]; String policyHeading = item['heading']; String policyName = item['policy_name']; - String si_value = item['si_value']; + // String si_value = item['si_value']; + String siValueStr = item['si_value'] ?? '0'; + String settledStr = item['total_settled_amount']?.toString() ?? '0'; + + double siValue = double.tryParse(siValueStr) ?? 0; + double settled = double.tryParse(settledStr) ?? 0; + +// Main logic + double finalValue = (settled == 0) ? siValue : (siValue - settled); + + print("Final Value: $finalValue"); String policyEndDate = item['policy_end_date']; List employeePolicy = item['EmployeePolicy']; String memberNames = getMemberNames(employeePolicy); @@ -1188,7 +1198,7 @@ class _HomeState extends State { children: [ Text( formatToCroresLakhsAndThousands( - si_value), + siValueStr), textAlign: TextAlign.left, style: GoogleFonts.poppins( fontSize: @@ -1247,7 +1257,34 @@ class _HomeState extends State { ), ], ), - SizedBox(height: 7), + SizedBox(height: 20), + Row( + children: [ + Expanded( + flex: 12, + child: Column( + crossAxisAlignment: + CrossAxisAlignment.end, + children: [ + Text( + 'Remaining Amount: ${finalValue}', + textAlign: TextAlign.right, + style: GoogleFonts.poppins( + fontSize: + Responsive.isDesktop( + context) + ? 10 + : 10, + fontWeight: FontWeight.w400, + color: Color(0xFF747474), + ), + ), + ], + ), + ), + ], + ), + // SizedBox(height: 7), ], ))), ], diff --git a/lib/pages/postEnrollment/planclaimsform.dart b/lib/pages/postEnrollment/planclaimsform.dart index 1577813..10f14e3 100755 --- a/lib/pages/postEnrollment/planclaimsform.dart +++ b/lib/pages/postEnrollment/planclaimsform.dart @@ -24,6 +24,8 @@ import 'package:file_picker/file_picker.dart'; import '../service/SessionManager.dart'; import '../service/TokenService.dart'; import '../service/popup_helper.dart'; +import 'package:pdf/widgets.dart' as pw; +import 'package:printing/printing.dart'; class planclaimsform extends StatefulWidget { final Map? details; @@ -634,29 +636,73 @@ class _planclaimsformState extends State { } // ✅ NEW VALIDATION: At least one PDF must be uploaded - bool hasPdf = uploadedFiles.any((uf) { - final ext = uf.file.extension?.toLowerCase() ?? ''; - return ext == 'pdf'; - }); - - if (!hasPdf) { - ToastHelper.showErrorToast(context, 'Please upload at least one PDF document'); - setState(() => isLoading = false); - return; - } + // bool hasPdf = uploadedFiles.any((uf) { + // final ext = uf.file.extension?.toLowerCase() ?? ''; + // return ext == 'pdf'; + // }); + // + // if (!hasPdf) { + // ToastHelper.showErrorToast(context, 'Please upload at least one PDF document'); + // setState(() => isLoading = false); + // return; + // } // Add files + // for (var uf in uploadedFiles) { + // final pf = uf.file; + // if (pf.bytes != null) { + // request.files.add(http.MultipartFile.fromBytes( + // 'claim_docs[]', + // pf.bytes!, + // filename: pf.name, + // )); + // } + // } + + // Add files — convert images to PDF if needed for (var uf in uploadedFiles) { final pf = uf.file; + final ext = pf.extension?.toLowerCase() ?? ''; + if (pf.bytes != null) { - request.files.add(http.MultipartFile.fromBytes( - 'claim_docs[]', - pf.bytes!, - filename: pf.name, - )); + Uint8List fileBytes = pf.bytes!; + + if (['jpg', 'jpeg', 'png', 'heic'].contains(ext)) { + // ✅ Convert image → PDF + final pdf = pw.Document(); + final image = pw.MemoryImage(fileBytes); + + pdf.addPage( + pw.Page( + build: (pw.Context context) => pw.Center( + child: pw.Image(image, fit: pw.BoxFit.contain), + ), + ), + ); + + fileBytes = await pdf.save(); // converted PDF bytes + + // replace file name with .pdf extension + final pdfFileName = pf.name.replaceAll(RegExp(r'\.\w+$'), '.pdf'); + + print('📄 Converted image ${pf.name} → PDF ($pdfFileName)'); + request.files.add(http.MultipartFile.fromBytes( + 'claim_docs[]', + fileBytes, + filename: pdfFileName, + )); + } else { + // ✅ Already a PDF + request.files.add(http.MultipartFile.fromBytes( + 'claim_docs[]', + fileBytes, + filename: pf.name, + )); + } } } + // ✅ Combine all names into a JSON array string final claimDocNames = uploadedFiles.map((uf) => uf.label.trim()).toList(); final encodedNames = jsonEncode(claimDocNames); diff --git a/lib/pages/postEnrollment/profile.dart b/lib/pages/postEnrollment/profile.dart index e421c11..c7639dc 100755 --- a/lib/pages/postEnrollment/profile.dart +++ b/lib/pages/postEnrollment/profile.dart @@ -787,10 +787,10 @@ class _profileState extends State { mainAxisSize: MainAxisSize.min, children: [ // ✅ Change Password Button - // if(Responsive.isDesktop(context)) - // _buildChangePasswordButton(context), + if(Responsive.isDesktop(context)) + _buildChangePasswordButton(context), - // const SizedBox(width: 10), + const SizedBox(width: 10), // ✅ Logout Button Responsive.isDesktop(context) diff --git a/lib/pages/postEnrollment/service/api_service.dart b/lib/pages/postEnrollment/service/api_service.dart index 718d372..5a5714b 100755 --- a/lib/pages/postEnrollment/service/api_service.dart +++ b/lib/pages/postEnrollment/service/api_service.dart @@ -146,6 +146,7 @@ class ApiService { await _initializeToken(); } final url = Uri.parse('${Environment.apiUrl}getWellnessURL?emp_id=$empPrimaryId'); + // final url = Uri.parse('${Environment.apiUrl}getWellnessURL?emp_id=$empPrimaryId&client_policy_id=$client_policy_id'); final headers = { 'Authorization': 'Bearer $_postToken' ?? '', }; diff --git a/lib/pages/setPassword.dart b/lib/pages/setPassword.dart index 24c21a9..9709cd5 100644 --- a/lib/pages/setPassword.dart +++ b/lib/pages/setPassword.dart @@ -41,12 +41,25 @@ class _setPasswordState extends State { bool _obscureNewPassword = true; bool _obscureConfirmPassword = true; late SessionManager session; + bool hasMinLength = false; + bool hasUpperLower = false; + bool hasNumber = false; + bool hasSpecialChar = false; + bool get isPasswordValid => hasMinLength && hasUpperLower && hasNumber && hasSpecialChar; @override void initState() { super.initState(); } + void validatePassword(String password) { + setState(() { + hasMinLength = password.length >= 8; + hasUpperLower = RegExp(r'(?=.*[A-Za-z])').hasMatch(password); + hasNumber = RegExp(r'(?=.*\d)').hasMatch(password); + hasSpecialChar = RegExp(r'(?=.*[@$!%*#?&])').hasMatch(password); + }); + } Future resetYourPassword() async { final newPassword = newPasswordController.text.trim(); @@ -348,7 +361,7 @@ class _setPasswordState extends State { MainAxisAlignment.center, children: [ Text( - "Welcome to Nhance", + "Set a new password", style: GoogleFonts.poppins( fontSize: 16, fontWeight: FontWeight.bold, @@ -372,7 +385,7 @@ class _setPasswordState extends State { children: [ Expanded( child: Text( - "Login with your to review and enroll for exciting health benefits for you and your family", + "Create a new password. Ensure it differs from previous ones for security", style: GoogleFonts.poppins( fontSize: 12, color: Color(0xFF000000)), @@ -391,70 +404,58 @@ class _setPasswordState extends State { // 🔹 Password Field Container( height: 55, - margin: Responsive.isDesktop( - context) - ? const EdgeInsets - .symmetric( - horizontal: 150) - : const EdgeInsets - .symmetric( - horizontal: 0), + margin: Responsive.isDesktop(context) + ? const EdgeInsets.symmetric(horizontal: 150) + : const EdgeInsets.symmetric(horizontal: 0), decoration: BoxDecoration( - border: Border.all( - width: 1, - color: Colors.grey), - borderRadius: - BorderRadius.circular( - 10), + border: Border.all(width: 1, color: Colors.grey), + borderRadius: BorderRadius.circular(10), ), child: TextFormField( - controller: - newPasswordController, - obscureText: - _obscureNewPassword, - textAlignVertical: - TextAlignVertical - .center, + controller: newPasswordController, + obscureText: _obscureNewPassword, + onChanged: validatePassword, + textAlignVertical: TextAlignVertical.center, decoration: InputDecoration( border: InputBorder.none, - hintText: - "New Password", - contentPadding: - const EdgeInsets - .symmetric( - horizontal: 10), + hintText: "New Password", + contentPadding: const EdgeInsets.symmetric(horizontal: 10), suffixIcon: IconButton( icon: Icon( - _obscureNewPassword - ? Icons - .visibility_off - : Icons - .visibility, + _obscureNewPassword ? Icons.visibility_off : Icons.visibility, color: Colors.grey, ), onPressed: () { setState(() { - _obscureNewPassword = - !_obscureNewPassword; + _obscureNewPassword = !_obscureNewPassword; }); }, ), ), - validator: (value) { - if (value == null || - value.isEmpty) { - return 'Please enter your new password'; - } - if (value.length < 6) { - return 'Password must be at least 6 characters'; - } - if (!RegExp( - r'^(?=.*[A-Z])(?=.*[0-9]).{6,}$') - .hasMatch(value)) { - return 'Include at least 1 uppercase letter and 1 number'; - } - return null; - }, + ), + ), + const SizedBox(height: 8), + // 🔹 VALIDATION LIST + Padding( + padding: Responsive.isDesktop(context) + ? const EdgeInsets.symmetric(horizontal: 150) + : const EdgeInsets.symmetric(horizontal: 5), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded(child: _buildCheckItem(hasMinLength, "Minimum 8 characters")), + Expanded(child: _buildCheckItem(hasSpecialChar, "1 special character")), + ], + ), + Row( + children: [ + Expanded(child: _buildCheckItem(hasUpperLower, "1 UPPER or lower case")), + Expanded(child: _buildCheckItem(hasNumber, "1 numerical")), + ], + ), + ], ), ), const SizedBox(height: 10), @@ -561,7 +562,7 @@ class _setPasswordState extends State { ), ) : Text( - "Reset Password", + "Update Password", style: GoogleFonts .poppins( color: Color( @@ -692,4 +693,28 @@ class _setPasswordState extends State { )), ))); } + + Widget _buildCheckItem(bool status, String text) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: Row( + children: [ + Icon( + status ? Icons.check : Icons.close, + color: status ? Colors.green : Colors.red, + size: 18, + ), + const SizedBox(width: 6), + Text( + text, + style: TextStyle( + color: status ? Colors.green : Colors.red, + fontSize: 14, + ), + ), + ], + ), + ); + } + } diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 3ccd551..2b0dbc9 100755 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -8,6 +8,7 @@ #include #include +#include #include void fl_register_plugins(FlPluginRegistry* registry) { @@ -17,6 +18,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); + g_autoptr(FlPluginRegistrar) printing_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "PrintingPlugin"); + printing_plugin_register_with_registrar(printing_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 9ce94c4..a828629 100755 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_linux flutter_secure_storage_linux + printing url_launcher_linux ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 5a09cb9..073fa95 100755 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -13,6 +13,7 @@ import google_sign_in_ios import local_auth_darwin import package_info_plus import path_provider_foundation +import printing import shared_preferences_foundation import sqflite_darwin import url_launcher_macos @@ -28,6 +29,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + PrintingPlugin.register(with: registry.registrar(forPlugin: "PrintingPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) diff --git a/pubspec.yaml b/pubspec.yaml index 21e4e70..b11c3fc 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -67,6 +67,7 @@ dependencies: universal_html: ^2.2.4 go_router: ^16.2.2 flutter_secure_storage: ^9.2.4 + printing: ^5.14.2 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 06dd00e..5e0f997 100755 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { @@ -21,6 +22,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); LocalAuthPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("LocalAuthPlugin")); + PrintingPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PrintingPlugin")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 442dfbc..1b55847 100755 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -7,6 +7,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_inappwebview_windows flutter_secure_storage_windows local_auth_windows + printing url_launcher_windows )