import 'dart:convert';
import 'dart:html' as html;
// import 'dart:ui' as html;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:frontend/config/apiUrl.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
import 'package:go_router/go_router.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../../services/apiService.dart';
class LoginWidget extends StatefulWidget {
final bool isDesktop;
final bool isTablet;
const LoginWidget({Key? key, required this.isDesktop, required this.isTablet})
: super(key: key);
@override
_LoginWidgetState createState() => _LoginWidgetState();
}
enum LoginStep { login, forgotEmail, otpReset }
class _LoginWidgetState extends State {
final ApiService apiService = ApiService();
bool _moved = false;
final _formKey = GlobalKey();
final TextEditingController _emailController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
final TextEditingController _otpController = TextEditingController();
final TextEditingController _newPasswordController = TextEditingController();
final TextEditingController _confirmPasswordController =
TextEditingController();
String? userRole;
bool _obscureText = true;
bool _isForgotPassword = false;
bool _showOtpResetFields = false;
// 🔹 Login Step Enum and State Variable
LoginStep _loginStep = LoginStep.login;
@override
void initState() {
super.initState();
Future.delayed(Duration(milliseconds: 300), () {
setState(() {
_moved = true;
});
});
}
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
_otpController.dispose();
_newPasswordController.dispose();
_confirmPasswordController.dispose();
super.dispose();
}
Future storeUserDetails(String token) async {
try {
final parts = token.split('.');
if (parts.length != 3) throw Exception('Invalid token format');
final payload = json.decode(
utf8.decode(base64Url.decode(base64Url.normalize(parts[1]))),
);
final userData = payload['data'];
final prefs = await SharedPreferences.getInstance();
await prefs.setString('auth_token', token);
await prefs.setString(
'user_data',
jsonEncode(userData),
); // Store full user data
if (userData != null) {
final pref = await SharedPreferences.getInstance();
await pref.setString('auth_token', token);
await pref.setString('user_data', jsonEncode(userData));
userRole = userData['role'];
print("userData - $userData");
print("userData11 - ${userData['role']}");
print("userData12 - $userRole");
}
await apiService.getOrganizationData();
} catch (e) {
print('Error decoding token: $e');
}
}
Future _login(BuildContext context) async {
if (_formKey.currentState!.validate()) {
const String url = '$apiUrl/auth/login';
try {
final response = await http.post(
Uri.parse(url),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: jsonEncode({
'email': _emailController.text.trim(),
'password': _passwordController.text.trim(),
}),
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
print("data- $data");
final token = data['token']; // Assuming the token is in response
// final userId = data['user_id'].toString();
await storeUserDetails(token);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Login Successful"),
backgroundColor: Colors.green, // Set background to green
),
);
if (userRole == "Travel Agent") {
context.go('/listTravelAgentPlan');
} else if (userRole == "Org Admin" || userRole == "Travel Admin") {
context.go('/StatusDashboard');
} else {
context.go('/listPlan');
}
} else {
final body = jsonDecode(response.body);
final messages = body['messages'];
String errorMessage = 'Unknown error';
if (messages is Map && messages.isNotEmpty) {
errorMessage = messages.values.first.toString();
}
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Login Failed: $errorMessage"),
backgroundColor: Colors.red,
),
);
_emailController.clear();
_passwordController.clear();
// Fluttertoast.showToast(
// msg: "Login Failed: $errorMessage",
// toastLength: Toast.LENGTH_LONG,
// gravity: ToastGravity.TOP_RIGHT,
// backgroundColor: Colors.red,
// textColor: Colors.white,
// fontSize: 16.0,
// );
}
} catch (e) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text("Error: $e")));
}
}
}
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"),
backgroundColor: Colors.green,
),
);
} else {
print(response);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
"${jsonDecode(response.body)['messages']['error']}",
),
backgroundColor: Colors.red,
),
);
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Error: $e"), backgroundColor: Colors.red),
);
}
} 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"),
backgroundColor: Colors.green,
),
);
} else {
print('23');
print('otp wrong');
final responseBody = json.decode(response.body);
final errorMessage =
responseBody['messages']?['error'] ?? 'An unknown error occurred';
print(errorMessage);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
"Reset failed: $errorMessage",
// "Reset failed: ${jsonDecode(response.body)['message']}",
),
backgroundColor: Colors.red,
),
);
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Error: $e"), backgroundColor: Colors.red),
);
}
} 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
Widget build(BuildContext context) {
double formWidth = widget.isTablet ? 400 : 300;
return Container(
// color: Color(0xFF114D8B),
color: Colors.white,
// color: Color(0xFFf5f5f5),
padding: const EdgeInsets.all(10),
child: Row(
children: [
if (widget.isDesktop)
Expanded(
flex: 2,
child: Container(
decoration: BoxDecoration(
color: Color(0xFFE6F0FA),
// color: Color(0xFFF0F7FF),
// color: Colors.white,
borderRadius: BorderRadius.only(
topRight: Radius.circular(250), // Rounded top-left corner
bottomRight: Radius.circular(
250,
), // Rounded bottom-left corner
),
),
// child: Padding(
// padding: const EdgeInsets.only(
// left: 0,
// ),
// child: Align(
// alignment: Alignment.topLeft,
// child: Image.asset(
// 'assets/images/login/logoNew.jpg',
// width: 200, // Optional: control size
// height: 100,
// fit: BoxFit.contain,
// )),
// ),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Image.asset(
// 'assets/images/login/logoNew.jpg',
// width: 200, // Optional: control size
// height: 100,
// fit: BoxFit.contain,
// ),
Container(
width: 200, // Optional: control size
height: 100,
),
Expanded(
child: Container(
child: Align(
// alignment: Alignment.bottomRight,
child: Image.asset(
'assets/images/login/login_travel.png',
// width: 200, // Optional: control size
// height: 100,
fit: BoxFit.contain,
),
),
),
),
],
),
),
),
Expanded(
flex: 1,
child: Container(
decoration: BoxDecoration(
// color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(25), // Rounded top-left corner
bottomLeft: Radius.circular(25), // Rounded bottom-left corner
),
),
child: Padding(
padding: const EdgeInsets.all(10),
child: _buildForm(width: formWidth), // Fixed form width
),
),
),
],
),
);
}
/// **Reusable Login Form**
Widget _buildForm({required double width}) {
return Column(
children: [
// width: width,
// Row(
// mainAxisAlignment: MainAxisAlignment.end,
// children: [
//
// ],
// ),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: width,
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset(
'assets/images/login/logoNew.jpg',
width: 180, // Optional: control size
height: 70,
fit: BoxFit.contain,
),
const SizedBox(height: 2),
Text(
"Sign In",
style: GoogleFonts.poppins(
fontSize: widget.isDesktop ? 20 : 18,
fontWeight: FontWeight.w700,
color: Colors.green,
// color: Color(0xFF212121),
),
),
const SizedBox(height: 5),
Text(
"Welcome To TripApprovalTool",
style: GoogleFonts.poppins(
fontSize: 11,
fontWeight: FontWeight.w400,
color: Color(0xFF212121),
),
),
const SizedBox(height: 10),
/// **Email Field**
if (!_isForgotPassword && !_showOtpResetFields) ...[
_buildLabel("Email Address"),
TextFormField(
controller: _emailController,
style: GoogleFonts.poppins(
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: 10),
/// **Password Field**
_buildLabel("Password"),
// TextFormField(
// controller: _passwordController,
// style: GoogleFonts.poppins(
// 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,
// ),
//
// onPressed:
// () => setState(
// () => _obscureText = !_obscureText,
// ),
// ),
// ),
//
// validator:
// (value) =>
// value == null || value.isEmpty
// ? 'Required Password'
// : null,
// ),
TextFormField(
controller: _passwordController,
style: GoogleFonts.poppins(
fontWeight: FontWeight.w600,
fontSize: 11,
),
obscureText: _obscureText,
textInputAction: TextInputAction.done,
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,
),
onPressed:
() => setState(
() => _obscureText = !_obscureText,
),
),
),
onFieldSubmitted: (_) {
if (_formKey.currentState!.validate()) {
_login(context);
}
},
validator:
(value) =>
value == null || value.isEmpty
? 'Required Password'
: null,
),
const SizedBox(height: 10),
/// **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: 5,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Sign In",
style: GoogleFonts.poppins(
fontWeight: FontWeight.w800,
fontSize: 13.5,
),
),
SizedBox(width: 3),
Icon(
Icons.arrow_forward_sharp,
color: Colors.white,
),
],
),
),
),
),
],
),
] else if (_isForgotPassword &&
!_showOtpResetFields) ...[
_buildLabel("Email Address"),
TextFormField(
controller: _emailController,
style: GoogleFonts.poppins(
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: 10),
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: 5,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Submit",
style: GoogleFonts.poppins(
fontWeight: FontWeight.w800,
fontSize: 13.5,
),
),
SizedBox(width: 3),
Icon(
Icons.arrow_forward_sharp,
color: Colors.white,
),
],
),
),
),
),
],
),
] else ...[
_buildLabel("Email Address"),
TextFormField(
controller: _emailController,
readOnly: true,
style: GoogleFonts.poppins(
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: 10),
_buildLabel("OTP"),
TextFormField(
controller: _otpController,
style: GoogleFonts.poppins(
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: 10),
_buildLabel("New Password"),
TextFormField(
controller: _newPasswordController,
style: GoogleFonts.poppins(
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: 10),
_buildLabel("Confirm Password"),
TextFormField(
controller: _confirmPasswordController,
style: GoogleFonts.poppins(
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: 10),
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: GoogleFonts.poppins(
fontWeight: FontWeight.w800,
fontSize: 13.5,
),
),
SizedBox(width: 3),
Icon(
Icons.arrow_forward_sharp,
color: Colors.white,
),
],
),
),
),
),
],
),
],
const SizedBox(height: 10),
if (!_isForgotPassword && !_showOtpResetFields)
Center(
child: TextButton(
onPressed: () {
setState(() {
_isForgotPassword = true;
_showOtpResetFields = false;
_passwordController.clear();
});
},
child: Text(
"Forgot Password",
style: GoogleFonts.poppins(
fontSize: 11,
fontWeight: FontWeight.w300,
color: Color(0xFF212121), // Text color
decoration:
TextDecoration
.underline, // Underline the text
),
),
),
),
if (_isForgotPassword || _showOtpResetFields)
Center(
child: TextButton(
onPressed: () {
setState(() {
_isForgotPassword = false;
_showOtpResetFields = false;
_clearAllFields();
});
},
child: Text(
"Back to Login",
style: GoogleFonts.poppins(
fontSize: 11,
fontWeight: FontWeight.w300,
color: Color(0xFF212121), // Text color
decoration:
TextDecoration
.underline, // Underline the text
),
),
),
),
const SizedBox(height: 10),
if (!_isForgotPassword && !_showOtpResetFields)
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () {
handleMS();
},
style: ElevatedButton.styleFrom(
backgroundColor:
Colors.white, // Button color
foregroundColor:
Colors.black, // Text color
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 5,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
side: BorderSide(
color: Colors.black, // Border color
width: 0.5, // Border width
),
),
),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 3,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Sign In With Microsoft",
style: GoogleFonts.poppins(
fontWeight: FontWeight.w500,
fontSize: 13,
),
),
SizedBox(width: 3),
Image.asset(
'assets/images/login/microsoft.png',
width: 30, // Optional: control size
height: 30,
fit: BoxFit.contain,
),
],
),
),
),
),
],
),
// Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// Container(
// width: 30, // Adjust size
// height: 30,
// 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: 20,
// fit: BoxFit.contain,
// )),
// ),
// ],
// ),
],
),
),
),
],
),
],
),
),
],
);
}
/// **Reusable Label Widget**
Widget _buildLabel(String text) {
return Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
text,
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF212121),
),
),
),
);
}
/// **Reusable Input Decoration**
InputDecoration _inputDecoration(String hint) {
return InputDecoration(
labelText: hint,
floatingLabelBehavior: FloatingLabelBehavior.never,
contentPadding: EdgeInsets.symmetric(vertical: 1.0, horizontal: 0.0),
filled: true,
fillColor: Colors.white,
labelStyle: GoogleFonts.poppins(fontSize: 11, color: Colors.grey),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(18),
borderSide: BorderSide(
color: Color(0xFF212121), // Border color
width: 1, // Optional: adjust the width of the border
),
),
);
}
Future handleMS() async {
final url = '$apiUrl/auth/mslogin';
print(url);
try {
final response = await http.get(
Uri.parse(url),
headers: {'Content-Type': 'application/json'},
);
print("inside try method");
if (response.statusCode == 200) {
final authUrl = json.decode(response.body)['auth_url'];
print("authurl - $authUrl");
if (authUrl != '') {
// final prefs = await SharedPreferences.getInstance();
// await prefs.setString('auth_token', authUrl);
print('i have auth URL');
// canLaunchUrl(authUrl);
if (kIsWeb) {
print("kIsWeb");
// Use web redirect (e.g., via JS interop or window.location.href)
// redirectTo(url);
html.window.location.href = authUrl;
} else {
// For mobile/desktop, open in external browser
// launchUrl(Uri.parse(url),
// mode: LaunchMode.externalApplication);
}
// final result = await FlutterWebAuth.authenticate(
// url: authUrl,
// callbackUrlScheme: "myapp", // Use a custom scheme you registered
// );
} else {
print('auth URL not Founded');
throw Exception('auth URL not Founded');
}
} else {
final errorMessage = json.decode(response.body)['message'];
print(errorMessage);
throw Exception(errorMessage);
}
} catch (e) {
print("Error: $e");
}
}
}