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"); } } }