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 setPassword extends StatefulWidget { final String email; final String client_id; final String route; const setPassword({ super.key, required this.email, required this.client_id, required this.route, }); @override State createState() => _setPasswordState(); } class _setPasswordState extends State { final TextEditingController newPasswordController = TextEditingController(); final TextEditingController confirmPasswordController = TextEditingController(); final _formKey = GlobalKey(); dynamic _preToken; dynamic _postToken; dynamic clientName; dynamic clientLogo; bool _isLoading = false; 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(); final confirmPassword = confirmPasswordController.text.trim(); try { if (_formKey.currentState!.validate()) { final strongPasswordRegex = RegExp(r'^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&]).{8,}$'); if (!strongPasswordRegex.hasMatch(newPassword)) { ToastHelper.showErrorToast( context, 'Enter a valid password (8+ chars with letter, number, special char)'); 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 + 'savePassword'; Map payload = { 'email_id': widget.email, 'client_id': widget.client_id, '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); bool userVerification = data['data']['user_verification']; String message = data['data']['message']; if (userVerification) { setState(() { _isLoading = false; }); ToastHelper.showSuccessToast(context, message!); context.go('/${widget.route}'); } 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 { // 🖥 Desktop / Web → always go back to login if (kIsWeb || Responsive.isDesktop(context)) { context.go('/login'); return false; } // 📱 Mobile → keep existing behavior (exit app) exit(0); return false; }, child: Scaffold( body: SingleChildScrollView( keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, child: Container( constraints: BoxConstraints(minHeight: _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( // 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: Alignment.center, 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: [ if (!Responsive.isMobile(context) && !Responsive.isTablet(context)) Row( children: [ // InkWell( // onTap: () { // context.go('/home'); // }, // child: Row( // mainAxisAlignment: MainAxisAlignment.start, // children: [ // // if (!Responsive.isDesktop(context)) // Icon( // Icons.chevron_left, // color: Color(0xFF000000), // size: 30, // ), // SizedBox( // width: Responsive.isDesktop(context) // ? 0 // : 5), // ], // ), // ), Expanded( flex: 12, child: Align( alignment: Alignment.center, 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) ? null : _size.height * 0.2, ), Container( margin: Responsive.isDesktop(context) ? EdgeInsets.symmetric( horizontal: 150) : EdgeInsets.symmetric( horizontal: 0), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( "Set a new 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( "Create a new password. Ensure it differs from previous ones for security", 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: 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( "Update Password", style: GoogleFonts .poppins( color: Color( 0xFFFFFFFF), ), ), ), ), ), // _size.height * 0.3 SizedBox( height: Responsive.isDesktop(context) ? null : _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(); } }, ), ), ], ), ], ), ), ), ), ], )), ))); } 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, ), ), ], ), ); } }