import 'dart:convert'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; 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/service/SessionManager.dart'; import 'dart:io'; import 'package:google_fonts/google_fonts.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../config/environment.dart'; import '../customAppBar/responsive.dart'; import '../customAppBar/toastHelper.dart'; import 'package:nhance_app_pwa/logger.dart'; class changesPassword extends StatefulWidget { final String email; final String client_id; const changesPassword({ super.key, required this.email, required this.client_id, }); @override State createState() => _changesPasswordState(); } class _changesPasswordState extends State { final TextEditingController oldPasswordController = TextEditingController(); final TextEditingController newPasswordController = TextEditingController(); final TextEditingController confirmPasswordController = TextEditingController(); final _formKey = GlobalKey(); dynamic _preToken; dynamic _postToken; dynamic clientName; dynamic clientLogo; bool _isLoading = false; bool _obscureOldPassword = true; 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(); final newPassword = newPasswordController.text.trim(); final confirmPassword = confirmPasswordController.text.trim(); try { if (_formKey.currentState!.validate()) { // Extra safety checks before calling the API if (oldpassword == newPassword) { ToastHelper.showErrorToast( context, 'New password cannot be same as old password'); return; } if (!isPasswordValid) { ToastHelper.showErrorToast( context, 'Password must be 8+ characters with 1 letter, 1 number, and 1 special character'); return; } if (confirmPassword != newPassword) { 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 + 'changePassword'; Map payload = { 'email_id': widget.email, 'client_id': widget.client_id, 'old_password': oldpassword, 'new_password': newPassword, 'confirm_password': confirmPassword }; // var enteredMobileNumber = mobileController.text; final response = await http.post( Uri.parse(apiEndpoint), body: json.encode(payload), headers: { HttpHeaders.contentTypeHeader: 'application/json', 'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y', }, ); if (response.statusCode == 200) { Map data = json.decode(response.body); String? statusVerification = data['status']; String? message = data['message']; if (statusVerification == 'success') { final SharedPreferences prefs = await SharedPreferences .getInstance(); await SessionManager().clear(); await prefs.clear(); setState(() { _isLoading = false; }); ToastHelper.showSuccessToast(context, message!); context.go('/login'); } else { setState(() { _isLoading = false; }); ToastHelper.showErrorToast(context, message!); logDebug('Invalid mobile number'); } } else if (response.statusCode == 401) { setState(() { _isLoading = false; }); await SessionManager().clear(); ToastHelper.showErrorToast(context, 'Session Out'); if (!context.mounted) return; context.go('/login'); } else if (response.statusCode == 403) { setState(() { _isLoading = false; }); await SessionManager().clear(); ToastHelper.showErrorToast(context, 'Session Out'); if (!context.mounted) return; context.go('/login'); } else if (response.statusCode == 451) { setState(() { _isLoading = false; }); final body = jsonDecode(response.body); final message = body['message']; ToastHelper.showWarningToast(context, message); } else if (response.statusCode == 429) { setState(() { _isLoading = false; }); final body = jsonDecode(response.body); final message = body['message']; ToastHelper.showWarningToast(context, message); } else { setState(() { _isLoading = false; }); throw Exception('Failed to load data'); } } } catch (e) { setState(() { _isLoading = false; }); // ToastHelper.showErrorToast(context, 'Unable to process. Please try again later'); logDebug('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; // } } //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, right: 0, bottom: 0, top: 0, ); } else if (Responsive.isMobile(context)) { marginInsets = const EdgeInsets.only( left: 25, // Example value for mobile right: 25, // Example value for mobile bottom: 0, // Example value for mobile top: 0, // Example value for mobile ); } else if (Responsive.isTablet(context)) { marginInsets = const EdgeInsets.only( left: 25, //// Example value for mobile right: 25, // Example value for mobile bottom: 0, // Example value for mobile top: 0, // Example value for mobile ); } return WillPopScope( onWillPop: () async { // Close the app on mobile back button press exit(0); // This will exit the app return false; // Return false to prevent any other actions }, child: Scaffold( body: SingleChildScrollView( keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, child: Container( height: Responsive.isDesktop(context) ? null : _size.height, constraints: Responsive.isDesktop(context) ? BoxConstraints(minHeight: _size.height) : null, 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( // children: [ // 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 // ), // ), // SizedBox(width: 5), // Icon( // Icons // .east, // Icon for customer login // color: Colors // .black, // Adjust color as needed // ), // ], // ), // ), // ), // ), // ], // ), // ], // ), // ], // ), // ), // ), // ), Container( margin: marginInsets, alignment: Responsive.isDesktop(context) ? Alignment.topCenter : Alignment.bottomCenter, child: SingleChildScrollView( child: Form( key: _formKey, child: Column( children: [ 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: [ // Desktop-only: center "<" and logo in one row if (Responsive.isDesktop(context)) Row( mainAxisAlignment: MainAxisAlignment.center, children: [ InkWell( onTap: () { context.go('/home'); }, child: const Icon( Icons.chevron_left, color: Color(0xFF000000), size: 30, ), ), const SizedBox(width: 8), Image.asset( 'assets/nhance_app_logo.png', width: 150, height: 150, // color: Colors.green, ), ], ), SizedBox( height: Responsive.isDesktop(context) ? null : 10, ), // SizedBox(height: 10), Container( margin: Responsive.isDesktop(context) ? EdgeInsets.symmetric( horizontal: 150) : EdgeInsets.symmetric( horizontal: 0), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( "Change Password", 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( "To change your password, please fill in the fields below.", style: GoogleFonts.poppins( fontSize: 12, color: Color(0xFF000000)), textAlign: TextAlign.center, ), ) ], ), ), SizedBox( height: 20, ), Column( children: [ // 🔹 Password Field 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: oldPasswordController, obscureText: _obscureOldPassword, textAlignVertical: TextAlignVertical .center, decoration: InputDecoration( border: InputBorder.none, hintText: "Old Password", contentPadding: const EdgeInsets .symmetric( horizontal: 10), suffixIcon: IconButton( icon: Icon( _obscureOldPassword ? Icons .visibility_off : Icons .visibility, color: Colors.grey, ), onPressed: () { setState(() { _obscureOldPassword = !_obscureOldPassword; }); }, ), ), validator: (value) { if (value == null || value.isEmpty) { return 'Please enter your old password'; } if (value.length < 8) { return 'Password must be at least 8 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: 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: newPasswordController, obscureText: _obscureNewPassword, onChanged: validatePassword, textAlignVertical: TextAlignVertical.center, decoration: InputDecoration( border: InputBorder.none, hintText: "New Password", contentPadding: const EdgeInsets.symmetric(horizontal: 10), suffixIcon: IconButton( icon: Icon( _obscureNewPassword ? Icons.visibility_off : Icons.visibility, color: Colors.grey, ), onPressed: () { setState(() { _obscureNewPassword = !_obscureNewPassword; }); }, ), ), validator: (value) { final password = value ?? ''; if (password.isEmpty) { return 'Please enter your new password'; } if (password.length < 8) { return 'Password must be at least 8 characters'; } if (!RegExp(r'[A-Za-z]').hasMatch(password)) { return 'Password must include at least 1 letter'; } if (!RegExp(r'\d').hasMatch(password)) { return 'Password must include at least 1 number'; } if (!RegExp(r'[@$!%*#?&]').hasMatch(password)) { return 'Password must include at least 1 special character'; } 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), 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 != newPasswordController .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: Color(0xFF00989E), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( 10), ), ), onPressed: _isLoading ? null : resetYourPassword, child: _isLoading ? CircularProgressIndicator( valueColor: AlwaysStoppedAnimation< Color>( Color(0xFF00989E), ), ) : Text( "Reset Password", style: GoogleFonts .poppins( color: Color( 0xFFFFFFFF), ), ), ), ), ), SizedBox( height: Responsive.isDesktop(context) ? null : _size.height * 0.2, ), // SizedBox( // height: _size.height * 0.1, // ), Container( alignment: Responsive.isDesktop(context) ? Alignment.center : 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(); } }, ), ), ], ), ], ), ), ), ), ], )), ))); } 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, ), ), ], ), ); } }