loginpage changes

This commit is contained in:
venbaittech 2025-05-05 09:05:01 +05:30
commit 1999e63c0f
4 changed files with 487 additions and 116 deletions

3
devtools_options.yaml Normal file
View File

@ -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:

View File

@ -597,15 +597,15 @@ class _ApprovalListState extends State<ApprovalList> {
fontSize: 13, fontSize: 13,
fontFamily: "Archivo", fontFamily: "Archivo",
))), ))),
DataCell(Text( DataCell(Text(plan.employeeCode ?? "no data",
plan.userName.isNotEmpty
? plan.userName
: plan.travellerName,
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
))), ))),
DataCell(Text(plan.employeeCode ?? "no data", DataCell(Text(
plan.userName.isNotEmpty
? plan.userName
: plan.travellerName,
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",

View File

@ -16,11 +16,31 @@ class LoginWidget extends StatefulWidget {
_LoginWidgetState createState() => _LoginWidgetState(); _LoginWidgetState createState() => _LoginWidgetState();
} }
enum LoginStep { login, forgotEmail, otpReset }
class _LoginWidgetState extends State<LoginWidget> { class _LoginWidgetState extends State<LoginWidget> {
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
final TextEditingController _emailController = TextEditingController(); final TextEditingController _emailController = TextEditingController();
final TextEditingController _passwordController = TextEditingController(); final TextEditingController _passwordController = TextEditingController();
final TextEditingController _otpController = TextEditingController();
final TextEditingController _newPasswordController = TextEditingController();
final TextEditingController _confirmPasswordController =
TextEditingController();
bool _obscureText = true; 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<void> storeUserDetails(String token) async { Future<void> storeUserDetails(String token) async {
try { try {
@ -47,7 +67,7 @@ class _LoginWidgetState extends State<LoginWidget> {
} }
} }
void _login(BuildContext context) async { Future<void> _login(BuildContext context) async {
if (_formKey.currentState!.validate()) { if (_formKey.currentState!.validate()) {
const String url = '$apiUrl/auth/login'; const String url = '$apiUrl/auth/login';
@ -95,6 +115,108 @@ class _LoginWidgetState extends State<LoginWidget> {
} }
} }
Future<void> _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 @override
/// Layout /// Layout
@ -188,130 +310,376 @@ class _LoginWidgetState extends State<LoginWidget> {
const SizedBox(height: 18), const SizedBox(height: 18),
/// **Email Field** /// **Email Field**
_buildLabel("Email Address"), if (!_isForgotPassword && !_showOtpResetFields) ...[
TextFormField( _buildLabel("Email Address"),
controller: _emailController, TextFormField(
style: TextStyle( controller: _emailController,
fontFamily: "Archivo", style: TextStyle(
fontWeight: FontWeight.w600, fontFamily: "Archivo",
fontSize: 11), fontWeight: FontWeight.w600,
decoration: _inputDecoration("Enter your email address").copyWith( fontSize: 11),
prefixIcon: Icon( decoration:
Icons.email_outlined, _inputDecoration("Enter your email address").copyWith(
size: 16, prefixIcon: Icon(
), Icons.email_outlined,
),
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),
size: 16, 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** /// **Password Field**
Row( _buildLabel("Password"),
children: [ TextFormField(
Expanded( controller: _passwordController,
child: ElevatedButton( style: TextStyle(
onPressed: () => _login(context), fontFamily: "Archivo",
style: ElevatedButton.styleFrom( fontWeight: FontWeight.w600,
backgroundColor: Color(0xFF12B24B), // Button color fontSize: 11),
foregroundColor: Colors.white, // Text color obscureText: _obscureText,
padding: const EdgeInsets.symmetric( decoration: _inputDecoration("Enter your password").copyWith(
horizontal: 24, vertical: 12), prefixIcon: Icon(
shape: RoundedRectangleBorder( Icons.key,
borderRadius: BorderRadius.circular(18)), size: 16,
),
suffixIcon: IconButton(
icon: Icon(
_obscureText ? Icons.visibility_off : Icons.visibility,
color: Color(0xFF12B24B),
size: 16,
), ),
child: Padding( onPressed: () =>
padding: const EdgeInsets.symmetric( setState(() => _obscureText = !_obscureText),
horizontal: 24, vertical: 10), ),
child: Row( ),
mainAxisSize: MainAxisSize.min, validator: (value) =>
children: [ value == null || value.isEmpty ? 'Required Password' : null,
Text( ),
"Sign In",
style: TextStyle( const SizedBox(height: 20),
fontFamily: "Nunito",
fontWeight: FontWeight.w800, /// **Login Button**
fontSize: 15), Row(
), children: [
SizedBox( Expanded(
width: 3, child: ElevatedButton(
), onPressed: () => _login(context),
Icon( style: ElevatedButton.styleFrom(
Icons.arrow_forward_sharp, backgroundColor: Color(0xFF12B24B), // Button color
color: Colors.white, 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,
)
],
),
), ),
), ),
), ),
), ],
], ),
), ] else if (_isForgotPassword && !_showOtpResetFields) ...[
const SizedBox(height: 20), _buildLabel("Email Address"),
Center( TextFormField(
child: Text( controller: _emailController,
"Forgot Password",
style: TextStyle( style: TextStyle(
fontSize: 11, fontFamily: "Archivo",
fontWeight: FontWeight.w300, fontWeight: FontWeight.w600,
fontFamily: "Nunito", fontSize: 11),
color: Color(0xFF212121), // Text color decoration:
decoration: TextDecoration.underline, // Underline the text _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
),
),
), ),
), ),
), if (_isForgotPassword || _showOtpResetFields)
Center(
const SizedBox(height: 20), child: TextButton(
onPressed: () {
Container( setState(() {
width: 45, // Adjust size _isForgotPassword = false;
height: 45, _showOtpResetFields = false;
decoration: BoxDecoration( _clearAllFields();
// Background color });
shape: BoxShape.rectangle, },
borderRadius: BorderRadius.all(Radius.circular(15)), child: Text(
border: Border.all( "Back to Login",
color: Color(0xFF9E9DBD), width: 1), // Grey outline style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w300,
fontFamily: "Nunito",
color: Color(0xFF212121), // Text color
decoration:
TextDecoration.underline, // Underline the text
),
),
),
), ),
child: Center( const SizedBox(height: 20),
child: Image.asset( if (!_isForgotPassword && !_showOtpResetFields)
'assets/images/login/VectorG.png', Container(
width: 20, // Optional: control size width: 45, // Adjust size
height: 10, height: 45,
fit: BoxFit.contain, 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,
)),
),
], ],
), ),
), ),

View File

@ -466,7 +466,7 @@ PopupMenuItem<String> buildMenuItem(Map<String, dynamic> item) {
item['label'], item['label'],
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Roboto", fontFamily: "Inter",
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
color: Colors.black87, color: Colors.black87,
), ),