diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/lib/Screens/approvals/approval_list.dart b/lib/Screens/approvals/approval_list.dart index 1dfca8a..d63d8d7 100644 --- a/lib/Screens/approvals/approval_list.dart +++ b/lib/Screens/approvals/approval_list.dart @@ -597,15 +597,15 @@ class _ApprovalListState extends State { fontSize: 13, fontFamily: "Archivo", ))), - DataCell(Text( - plan.userName.isNotEmpty - ? plan.userName - : plan.travellerName, + DataCell(Text(plan.employeeCode ?? "no data", style: TextStyle( fontSize: 13, fontFamily: "Inter", ))), - DataCell(Text(plan.employeeCode ?? "no data", + DataCell(Text( + plan.userName.isNotEmpty + ? plan.userName + : plan.travellerName, style: TextStyle( fontSize: 13, fontFamily: "Inter", diff --git a/lib/Screens/authentication/login/login_widget.dart b/lib/Screens/authentication/login/login_widget.dart index 36d5904..5b15bf1 100644 --- a/lib/Screens/authentication/login/login_widget.dart +++ b/lib/Screens/authentication/login/login_widget.dart @@ -16,11 +16,31 @@ class LoginWidget extends StatefulWidget { _LoginWidgetState createState() => _LoginWidgetState(); } +enum LoginStep { login, forgotEmail, otpReset } + class _LoginWidgetState extends State { final _formKey = GlobalKey(); final TextEditingController _emailController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); + final TextEditingController _otpController = TextEditingController(); + final TextEditingController _newPasswordController = TextEditingController(); + final TextEditingController _confirmPasswordController = + TextEditingController(); bool _obscureText = true; + bool _isForgotPassword = false; + bool _showOtpResetFields = false; + // 🔹 Login Step Enum and State Variable + LoginStep _loginStep = LoginStep.login; + + @override + void dispose() { + _emailController.dispose(); + _passwordController.dispose(); + _otpController.dispose(); + _newPasswordController.dispose(); + _confirmPasswordController.dispose(); + super.dispose(); + } Future storeUserDetails(String token) async { try { @@ -47,7 +67,7 @@ class _LoginWidgetState extends State { } } - void _login(BuildContext context) async { + Future _login(BuildContext context) async { if (_formKey.currentState!.validate()) { const String url = '$apiUrl/auth/login'; @@ -95,6 +115,108 @@ class _LoginWidgetState extends State { } } + Future _handleSubmit(BuildContext context) async { + print('2'); + if (!_formKey.currentState!.validate()) return; + print('3'); + if (_isForgotPassword && !_showOtpResetFields) { + print('11'); + // Step 1: Send OTP + final url = '$apiUrl/forgotPassword/verifyUser'; + try { + final response = await http.post( + Uri.parse(url), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode({'email': _emailController.text.trim()}), + ); + + if (response.statusCode == 200) { + setState(() => _showOtpResetFields = true); + setState(() { + _isForgotPassword = false; + _showOtpResetFields = true; + // _clearAllFields(); + }); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("OTP sent to your email")), + ); + } else { + print(response); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: + Text("${jsonDecode(response.body)['messages']['error']}")), + ); + } + } catch (e) { + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text("Error: $e"))); + } + } else if (!_isForgotPassword && _showOtpResetFields) { + print('22'); + // Step 2: Verify OTP & Reset Password + final url = '$apiUrl/forgotPassword/changePassword'; + try { + print('21'); + final response = await http.post( + Uri.parse(url), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode({ + 'email': _emailController.text.trim(), + 'otp': _otpController.text.trim(), + 'new_password': _newPasswordController.text.trim(), + 'confirm_password': _confirmPasswordController.text.trim(), + }), + ); + + if (response.statusCode == 200) { + print('22'); + setState(() { + _isForgotPassword = false; + _showOtpResetFields = false; + _clearAllFields(); + }); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Password reset successfully")), + ); + } else { + print('23'); + print('otp wrong'); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + "Reset failed: ${jsonDecode(response.body)['message']}")), + ); + } + } catch (e) { + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text("Error: $e"))); + } + } else { + // Login flow + _login(context); + } + } + + void _clearAllFields() { + _emailController.clear(); + _passwordController.clear(); + _otpController.clear(); + _newPasswordController.clear(); + _confirmPasswordController.clear(); + } + + void _onSubmit(BuildContext context) async { + if (_formKey.currentState!.validate()) { + if (!_isForgotPassword && !_showOtpResetFields) { + await _login(context); + } else { + print('1'); + _handleSubmit(context); + } + } + } + @override /// Layout @@ -188,130 +310,376 @@ class _LoginWidgetState extends State { const SizedBox(height: 18), /// **Email Field** - _buildLabel("Email Address"), - TextFormField( - controller: _emailController, - style: TextStyle( - fontFamily: "Archivo", - fontWeight: FontWeight.w600, - fontSize: 11), - decoration: _inputDecoration("Enter your email address").copyWith( - prefixIcon: Icon( - Icons.email_outlined, - size: 16, - ), - ), - validator: (value) => - value == null || value.isEmpty ? 'Required Email' : null, - ), - - const SizedBox(height: 16), - - /// **Password Field** - _buildLabel("Password"), - TextFormField( - controller: _passwordController, - style: TextStyle( - fontFamily: "Archivo", - fontWeight: FontWeight.w600, - fontSize: 11), - obscureText: _obscureText, - decoration: _inputDecoration("Enter your password").copyWith( - prefixIcon: Icon( - Icons.key, - size: 16, - ), - suffixIcon: IconButton( - icon: Icon( - _obscureText ? Icons.visibility_off : Icons.visibility, - color: Color(0xFF12B24B), + if (!_isForgotPassword && !_showOtpResetFields) ...[ + _buildLabel("Email Address"), + TextFormField( + controller: _emailController, + style: TextStyle( + fontFamily: "Archivo", + fontWeight: FontWeight.w600, + fontSize: 11), + decoration: + _inputDecoration("Enter your email address").copyWith( + prefixIcon: Icon( + Icons.email_outlined, size: 16, ), - onPressed: () => setState(() => _obscureText = !_obscureText), ), + validator: (value) => + value == null || value.isEmpty ? 'Required Email' : null, ), - validator: (value) => - value == null || value.isEmpty ? 'Required Password' : null, - ), - const SizedBox(height: 20), + const SizedBox(height: 16), - /// **Login Button** - Row( - children: [ - Expanded( - child: ElevatedButton( - onPressed: () => _login(context), - style: ElevatedButton.styleFrom( - backgroundColor: Color(0xFF12B24B), // Button color - foregroundColor: Colors.white, // Text color - padding: const EdgeInsets.symmetric( - horizontal: 24, vertical: 12), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(18)), + /// **Password Field** + _buildLabel("Password"), + TextFormField( + controller: _passwordController, + style: TextStyle( + fontFamily: "Archivo", + fontWeight: FontWeight.w600, + fontSize: 11), + obscureText: _obscureText, + decoration: _inputDecoration("Enter your password").copyWith( + prefixIcon: Icon( + Icons.key, + size: 16, + ), + suffixIcon: IconButton( + icon: Icon( + _obscureText ? Icons.visibility_off : Icons.visibility, + color: Color(0xFF12B24B), + size: 16, ), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 24, vertical: 10), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - "Sign In", - style: TextStyle( - fontFamily: "Nunito", - fontWeight: FontWeight.w800, - fontSize: 15), - ), - SizedBox( - width: 3, - ), - Icon( - Icons.arrow_forward_sharp, - color: Colors.white, - ) - ], + onPressed: () => + setState(() => _obscureText = !_obscureText), + ), + ), + validator: (value) => + value == null || value.isEmpty ? 'Required Password' : null, + ), + + const SizedBox(height: 20), + + /// **Login Button** + Row( + children: [ + Expanded( + child: ElevatedButton( + onPressed: () => _login(context), + style: ElevatedButton.styleFrom( + backgroundColor: Color(0xFF12B24B), // Button color + foregroundColor: Colors.white, // Text color + padding: const EdgeInsets.symmetric( + horizontal: 24, vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(18)), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 24, vertical: 10), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Sign In", + style: TextStyle( + fontFamily: "Nunito", + fontWeight: FontWeight.w800, + fontSize: 15), + ), + SizedBox( + width: 3, + ), + Icon( + Icons.arrow_forward_sharp, + color: Colors.white, + ) + ], + ), ), ), ), - ), - ], - ), - const SizedBox(height: 20), - Center( - child: Text( - "Forgot Password", + ], + ), + ] else if (_isForgotPassword && !_showOtpResetFields) ...[ + _buildLabel("Email Address"), + TextFormField( + controller: _emailController, style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.w300, - fontFamily: "Nunito", - color: Color(0xFF212121), // Text color - decoration: TextDecoration.underline, // Underline the text + fontFamily: "Archivo", + fontWeight: FontWeight.w600, + fontSize: 11), + decoration: + _inputDecoration("Enter your email address").copyWith( + prefixIcon: Icon( + Icons.email_outlined, + size: 16, + ), + ), + validator: (value) => + value == null || value.isEmpty ? 'Required Email' : null, + ), + const SizedBox(height: 20), + Row( + children: [ + Expanded( + child: ElevatedButton( + onPressed: () => _onSubmit(context), + style: ElevatedButton.styleFrom( + backgroundColor: Color(0xFF12B24B), // Button color + foregroundColor: Colors.white, // Text color + padding: const EdgeInsets.symmetric( + horizontal: 24, vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(18)), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 24, vertical: 10), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Submit", + style: TextStyle( + fontFamily: "Nunito", + fontWeight: FontWeight.w800, + fontSize: 15), + ), + SizedBox( + width: 3, + ), + Icon( + Icons.arrow_forward_sharp, + color: Colors.white, + ) + ], + ), + ), + ), + ), + ], + ), + ] else ...[ + _buildLabel("Email Address"), + TextFormField( + controller: _emailController, + readOnly: true, + style: TextStyle( + fontFamily: "Archivo", + fontWeight: FontWeight.w600, + fontSize: 11), + decoration: + _inputDecoration("Enter your email address").copyWith( + prefixIcon: Icon( + Icons.email_outlined, + size: 16, + ), + ), + validator: (value) => + value == null || value.isEmpty ? 'Required Email' : null, + ), + const SizedBox(height: 16), + _buildLabel("OTP"), + TextFormField( + controller: _otpController, + style: TextStyle( + fontFamily: "Archivo", + fontWeight: FontWeight.w600, + fontSize: 11), + decoration: _inputDecoration("Enter your OTP").copyWith( + prefixIcon: Icon( + Icons.email_outlined, + size: 16, + ), + ), + validator: (value) => + value == null || value.isEmpty ? 'Required OTP' : null, + ), + const SizedBox(height: 16), + _buildLabel("New Password"), + TextFormField( + controller: _newPasswordController, + style: TextStyle( + fontFamily: "Archivo", + fontWeight: FontWeight.w600, + fontSize: 11), + obscureText: _obscureText, + decoration: + _inputDecoration("Enter your new password").copyWith( + prefixIcon: Icon( + Icons.key, + size: 16, + ), + suffixIcon: IconButton( + icon: Icon( + _obscureText ? Icons.visibility_off : Icons.visibility, + color: Color(0xFF12B24B), + size: 16, + ), + onPressed: () => + setState(() => _obscureText = !_obscureText), + ), + ), + validator: (value) => value == null || value.isEmpty + ? 'Required New Password' + : null, + ), + const SizedBox(height: 16), + _buildLabel("Confirm Password"), + TextFormField( + controller: _confirmPasswordController, + style: TextStyle( + fontFamily: "Archivo", + fontWeight: FontWeight.w600, + fontSize: 11), + obscureText: _obscureText, + decoration: + _inputDecoration("Enter your confirm password").copyWith( + prefixIcon: Icon( + Icons.key, + size: 16, + ), + suffixIcon: IconButton( + icon: Icon( + _obscureText ? Icons.visibility_off : Icons.visibility, + color: Color(0xFF12B24B), + size: 16, + ), + onPressed: () => + setState(() => _obscureText = !_obscureText), + ), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Required New Password'; + } + if (value != _newPasswordController.text) { + return 'Passwords do not match'; + } + return null; + }, + ), + const SizedBox(height: 20), + Row( + children: [ + Expanded( + child: ElevatedButton( + onPressed: () { + if (!_formKey.currentState!.validate()) return; + setState(() { + _isForgotPassword = false; + _showOtpResetFields = true; + _passwordController.clear(); + }); + _onSubmit(context); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Color(0xFF12B24B), // Button color + foregroundColor: Colors.white, // Text color + padding: const EdgeInsets.symmetric( + horizontal: 24, vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(18)), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 24, vertical: 10), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Submit", + style: TextStyle( + fontFamily: "Nunito", + fontWeight: FontWeight.w800, + fontSize: 15), + ), + SizedBox( + width: 3, + ), + Icon( + Icons.arrow_forward_sharp, + color: Colors.white, + ) + ], + ), + ), + ), + ), + ], + ), + ], + const SizedBox(height: 20), + if (!_isForgotPassword && !_showOtpResetFields) + Center( + child: TextButton( + onPressed: () { + setState(() { + _isForgotPassword = true; + _showOtpResetFields = false; + _passwordController.clear(); + }); + }, + child: Text( + "Forgot Password", + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w300, + fontFamily: "Nunito", + color: Color(0xFF212121), // Text color + decoration: + TextDecoration.underline, // Underline the text + ), + ), ), ), - ), - - const SizedBox(height: 20), - - Container( - width: 45, // Adjust size - height: 45, - decoration: BoxDecoration( - // Background color - shape: BoxShape.rectangle, - borderRadius: BorderRadius.all(Radius.circular(15)), - border: Border.all( - color: Color(0xFF9E9DBD), width: 1), // Grey outline + if (_isForgotPassword || _showOtpResetFields) + Center( + child: TextButton( + onPressed: () { + setState(() { + _isForgotPassword = false; + _showOtpResetFields = false; + _clearAllFields(); + }); + }, + child: Text( + "Back to Login", + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w300, + fontFamily: "Nunito", + color: Color(0xFF212121), // Text color + decoration: + TextDecoration.underline, // Underline the text + ), + ), + ), ), - child: Center( - child: Image.asset( - 'assets/images/login/VectorG.png', - width: 20, // Optional: control size - height: 10, - fit: BoxFit.contain, - )), - ), + const SizedBox(height: 20), + if (!_isForgotPassword && !_showOtpResetFields) + Container( + width: 45, // Adjust size + height: 45, + decoration: BoxDecoration( + // Background color + shape: BoxShape.rectangle, + borderRadius: BorderRadius.all(Radius.circular(15)), + border: Border.all( + color: Color(0xFF9E9DBD), width: 1), // Grey outline + ), + + child: Center( + child: Image.asset( + 'assets/images/login/VectorG.png', + width: 20, // Optional: control size + height: 10, + fit: BoxFit.contain, + )), + ), ], ), ), diff --git a/lib/routes/custom_appBar.dart b/lib/routes/custom_appBar.dart index 0098d86..0e3a7e4 100644 --- a/lib/routes/custom_appBar.dart +++ b/lib/routes/custom_appBar.dart @@ -466,7 +466,7 @@ PopupMenuItem buildMenuItem(Map item) { item['label'], style: TextStyle( fontSize: 13, - fontFamily: "Roboto", + fontFamily: "Inter", fontWeight: FontWeight.w400, color: Colors.black87, ),