From b0c5ebce3deb7da2aa24b0ea5ffe684df7dfb7bf Mon Sep 17 00:00:00 2001 From: venbaittech Date: Fri, 13 Dec 2024 17:29:09 +0530 Subject: [PATCH] New change password flow added --- .run/main.dart.run.xml | 2 +- lib/config/my_router.dart | 9 + .../auth_verification/create_new_pw.dart | 387 ++++++++++++++++++ .../auth_verification/registration.dart | 21 +- lib/presentation/Screens/profilepage.dart | 10 +- .../routes/auth_routes/login_route.dart | 6 +- .../Drawer Items/edit_profile.dart | 38 +- .../Drawer Items/manage_users.dart | 30 +- 8 files changed, 434 insertions(+), 69 deletions(-) create mode 100644 lib/presentation/Screens/auth_verification/create_new_pw.dart diff --git a/.run/main.dart.run.xml b/.run/main.dart.run.xml index 4767aff8..4a58a7c9 100644 --- a/.run/main.dart.run.xml +++ b/.run/main.dart.run.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/lib/config/my_router.dart b/lib/config/my_router.dart index 806ed3e4..f413c71d 100644 --- a/lib/config/my_router.dart +++ b/lib/config/my_router.dart @@ -299,6 +299,7 @@ import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes. import '../domain/use_cases/preferences_use_case.dart'; import '../presentation/Screens/auth_verification/changepassword.dart'; import '../presentation/Screens/auth_verification/confirm_password.dart'; +import '../presentation/Screens/auth_verification/create_new_pw.dart'; import '../presentation/Screens/auth_verification/otp_verification.dart'; import '../presentation/Screens/profilepage.dart'; import '../presentation/routes/auth_routes/login_route.dart'; @@ -335,6 +336,14 @@ final GoRouter router = GoRouter( return Changepassword(userId: userId); }, ), + GoRoute( + path: '/createNewPw/:userId/:email', + builder: (context, state) { + final userId = state.pathParameters['userId']!; + final email = state.pathParameters['email']!; + return CreateNewPw(userId: userId, email: email); + }, + ), GoRoute( path: '/confirmpasswd', builder: (context, state) => ConfirmPassword( diff --git a/lib/presentation/Screens/auth_verification/create_new_pw.dart b/lib/presentation/Screens/auth_verification/create_new_pw.dart new file mode 100644 index 00000000..196b760c --- /dev/null +++ b/lib/presentation/Screens/auth_verification/create_new_pw.dart @@ -0,0 +1,387 @@ +import 'package:external_repos/external_repos.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:pocketbase/pocketbase.dart'; +import 'package:uae_stat/config/my_router.dart'; +import 'package:uae_stat/domain/use_cases/language.dart'; +import 'package:uae_stat/presentation/Screens/profilepage.dart'; +import 'package:uae_stat/presentation/components/space.dart'; + +import '../../../config/my_theme.dart'; + +class CreateNewPw extends StatefulWidget { + final String userId; + final String email; + const CreateNewPw({Key? key, required this.userId, required this.email}); + + @override + State createState() => _CreateNewPwState(); +} + +class _CreateNewPwState extends State { + final _pb = PocketBase('https://pb.venbait.in'); + final _formKey = GlobalKey(); + bool _obscureOldPassword = true; + bool _obscureNewPassword = true; + bool _obscureConfirmPassword = true; + bool _isPasswordUpdated = false; // Variable to toggle UI + + String? _oldPassword; + String? _newPassword; + String? _confirmPassword; + + String? _validateOldPassword(String? value) { + if (value == null || value.isEmpty) { + return 'Old password is required'; + } + return null; + } + + String? _validateNewPassword(String? value) { + if (value == null || value.isEmpty) { + return 'New password is required'; + } else if (value.length < 6) { + return 'Password must be at least 6 characters'; + } else if (value == _oldPassword) { + return 'New password must not be the same as the old password'; + } + _newPassword = value; // Store for validation + return null; + } + + String? _validateConfirmPassword(String? value) { + if (value == null || value.isEmpty) { + return 'Confirm password is required'; + } else if (value != _newPassword) { + return 'Passwords do not match'; + } + _confirmPassword = value; + return null; + } + + Future verifyUserPassword(String email, String password) async { + try { + // Attempt to authenticate the user with email and password + final authResponse = + await _pb.collection('users').authWithPassword(email, password); + + if (authResponse != null) { + print('Password match successful for email: $email'); + return true; // Password matches + } + } catch (e) { + // Authentication failed, password does not match + print('Error verifying password: $e'); + } + + return false; // Password does not match + } + + Future updatePassword( + String userId, String oldPassword, String newPassword) async { + try { + // Authenticate as admin + final adminAuth = await _pb.admins + .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); + final token = adminAuth.token; + + final headers = { + 'Authorization': 'Bearer $token', + }; + + bool isPasswordValid = + await verifyUserPassword(widget.email, oldPassword); + + print(isPasswordValid); + + if (isPasswordValid) { + // Update password + await _pb.collection('users').update( + userId, + body: { + 'password': newPassword, + 'passwordConfirm': newPassword, + }, + headers: headers, + ); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Password updated successfully.'), + backgroundColor: Colors.green, + ), + ); + + setState(() { + _isPasswordUpdated = true; // Toggle UI on success + }); + + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (context) => ProfileScreen(userId: userId)), + // ); + } else { + setState(() { + _formKey.currentState?.validate(); + }); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Your old password is incorrect.'), + backgroundColor: Colors.red, + ), + ); + return; + } + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Failed to update password: $e'), + backgroundColor: Colors.red, + ), + ); + } + } + + @override + Widget build(BuildContext context) { + double screenHeight = MediaQuery.of(context).size.height; + double screenWidth = MediaQuery.of(context).size.width; + + return Scaffold( + backgroundColor: Colors.white, + body: SingleChildScrollView( + child: SafeArea( + child: _isPasswordUpdated + ? _buildSuccessContent(screenHeight, screenWidth) + : _buildPasswordForm(screenHeight, screenWidth), + ), + ), + ); + } + + Widget _buildPasswordForm(double screenHeight, double screenWidth) { + return Form( + key: _formKey, + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + children: [ + SizedBox(height: screenHeight / 6), + Text( + "Create New Password", + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: screenHeight / 35), + TextFormField( + obscureText: _obscureOldPassword, + decoration: InputDecoration( + hintText: 'Enter old password', + prefixIcon: Icon(Icons.lock, color: Colors.blue), + suffixIcon: IconButton( + icon: Icon( + _obscureOldPassword + ? Icons.visibility + : Icons.visibility_off, + color: Colors.blue, + ), + onPressed: () { + setState(() { + _obscureOldPassword = !_obscureOldPassword; + }); + }, + ), + border: OutlineInputBorder(), + ), + validator: _validateOldPassword, + onChanged: (value) => _oldPassword = value, + ), + SizedBox(height: screenHeight / 35), + TextFormField( + obscureText: _obscureNewPassword, + decoration: InputDecoration( + hintText: 'Enter new password', + prefixIcon: Icon(Icons.lock, color: Colors.blue), + suffixIcon: IconButton( + icon: Icon( + _obscureNewPassword + ? Icons.visibility + : Icons.visibility_off, + color: Colors.blue, + ), + onPressed: () { + setState(() { + _obscureNewPassword = !_obscureNewPassword; + }); + }, + ), + border: OutlineInputBorder(), + ), + validator: _validateNewPassword, + ), + SizedBox(height: screenHeight / 35), + TextFormField( + obscureText: _obscureConfirmPassword, + decoration: InputDecoration( + hintText: 'Confirm new password', + prefixIcon: Icon(Icons.lock, color: Colors.blue), + suffixIcon: IconButton( + icon: Icon( + _obscureConfirmPassword + ? Icons.visibility + : Icons.visibility_off, + color: Colors.blue, + ), + onPressed: () { + setState(() { + _obscureConfirmPassword = !_obscureConfirmPassword; + }); + }, + ), + border: OutlineInputBorder(), + ), + validator: _validateConfirmPassword, + ), + SizedBox(height: screenHeight / 35), + SizedBox( + width: screenWidth / 1.1, + child: ElevatedButton( + onPressed: () async { + if ((_formKey.currentState?.validate() ?? false)) { + await updatePassword( + widget.userId, + _oldPassword ?? '', + _newPassword ?? '', + ); + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.brown[300], + padding: EdgeInsets.symmetric(horizontal: 80, vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Save", + style: TextStyle(fontSize: 18, color: Colors.white), + ), + SizedBox(width: 2), + Icon( + Icons.arrow_forward_ios, + color: Colors.white38, + size: 18, + ), + ], + ), + ), + ), + SizedBox(height: screenHeight / 5), + Center( + child: Container( + height: screenHeight / 8, + width: screenWidth / 2, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("assets/splash_screen/logo.png"), + fit: BoxFit.fill, + ), + ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildSuccessContent(double screenHeight, double screenWidth) { + return Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(height: screenHeight / 8), + Icon(Icons.check_circle_outlined, color: Color(0xFF8AC681), size: 80), + SizedBox(height: 20), + Text( + "Password Changed Successfully", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Color(0xFF414042), + ), + ), + SizedBox(height: 10), + ElevatedButton( + onPressed: () async { + context.go('/'); + }, + style: ButtonStyle( + shape: WidgetStatePropertyAll( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + padding: const WidgetStatePropertyAll( + EdgeInsets.symmetric(vertical: 10.5), + ), + textStyle: WidgetStatePropertyAll( + TextStyle( + fontFamily: context.translate( + 'Roboto', + 'NotoKufi', + ), + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + backgroundColor: WidgetStatePropertyAll( + MyTheme.topicColor(IndicatorTopic.social), + ), + // surfaceTintColor: MaterialStatePropertyAll( + // MyTheme.economy[800], + // ), + foregroundColor: const WidgetStatePropertyAll( + Colors.white, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + context.translate( + 'Login', + 'تسجيل الدخول', + ), + ), + 6.horizontalSpace, + const Icon(Icons.chevron_right_outlined), + ], + ), + ), + SizedBox(height: screenHeight / 5), + Center( + child: Container( + height: screenHeight / 8, + width: screenWidth / 2, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("assets/splash_screen/logo.png"), + fit: BoxFit.fill, + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/Screens/auth_verification/registration.dart b/lib/presentation/Screens/auth_verification/registration.dart index 97204028..41c6d0e1 100644 --- a/lib/presentation/Screens/auth_verification/registration.dart +++ b/lib/presentation/Screens/auth_verification/registration.dart @@ -275,7 +275,7 @@ class _RegisterScreenState extends State { buildIconContainer(Icons.report, Color(0xFF7DAFBC)), SizedBox(height: 20), Text( - "Your registration is pending for Admin Approval.", + "Your registration is pending for verification", textAlign: TextAlign.center, style: TextStyle( fontSize: 16, @@ -284,7 +284,7 @@ class _RegisterScreenState extends State { ), SizedBox(height: 10), Text( - "Access will be granted once your account is approved.", + "Kindly verify your mail to proceed further.", textAlign: TextAlign.center, style: TextStyle( fontSize: 14, @@ -293,14 +293,11 @@ class _RegisterScreenState extends State { ), SizedBox(height: 10), ElevatedButton( - onPressed: () => { + onPressed: () { Navigator.pushReplacement( context, - MaterialPageRoute(builder: (context) => LoginRoute() - - //ProfileScreen(userId: userID) - ), - ), + MaterialPageRoute(builder: (context) => LoginRoute()), + ); }, child: Text( 'Go to Login', @@ -455,8 +452,8 @@ class _RegisterScreenState extends State { suffixIcon: IconButton( icon: Icon( _obscurePassword - ? Icons.visibility - : Icons.visibility_off, + ? Icons.visibility_off + : Icons.visibility, color: Colors.blue, ), onPressed: () { @@ -487,8 +484,8 @@ class _RegisterScreenState extends State { suffixIcon: IconButton( icon: Icon( _obscureConfirmPassword - ? Icons.visibility - : Icons.visibility_off, + ? Icons.visibility_off + : Icons.visibility, color: Colors.blue, ), onPressed: () { diff --git a/lib/presentation/Screens/profilepage.dart b/lib/presentation/Screens/profilepage.dart index 76b9622b..48ce4ac6 100644 --- a/lib/presentation/Screens/profilepage.dart +++ b/lib/presentation/Screens/profilepage.dart @@ -597,12 +597,10 @@ class _ProfileScreenState extends State { Center( child: GestureDetector( onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - Changepassword(userId: widget.userId)), - ); + final userId = widget.userId; + final email = _emailController.text; + + context.go('/createNewPw/$userId/$email'); }, child: Text( "Change Password", diff --git a/lib/presentation/routes/auth_routes/login_route.dart b/lib/presentation/routes/auth_routes/login_route.dart index a0ea8df4..5ebb61e0 100644 --- a/lib/presentation/routes/auth_routes/login_route.dart +++ b/lib/presentation/routes/auth_routes/login_route.dart @@ -350,10 +350,10 @@ class LoginRoute extends HookConsumerWidget { 15.verticalSpace, ThemedFormField( validator: (text) { - if (text!.length < 10) { - return 'The password must be at least 10 characters'; + if (text!.length < 8) { + return 'The password must be at least 8 characters'; } - return FieldValidator.password(minLength: 10)(text); + return FieldValidator.password(minLength: 8)(text); }, hintText: context.translate( 'Password', diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart b/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart index 3d74ef02..080be24f 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart @@ -135,7 +135,6 @@ class _EditProfileState extends State { Future checkUserId() async { userId = await getUserId(); - //userId = 'tplu4by67phkfoq'; if (userId != null && userId.isNotEmpty) { print('User ID: $userId'); _fetchUserData(); @@ -189,25 +188,6 @@ class _EditProfileState extends State { } else { _avatarUrl = ''; // Reset to default or empty } - - // String avatarFilename = userDetailsResponse.data['avatar'] ?? ''; - // //print("PROFILE AVAILABLE -$avatarFilename"); - // String recordId = userId; - // //String recordId = userDetailsResponse.data['id'] ?? ""; - // print("PROFILE AVAILABLE -${userDetailsResponse.data['id']}"); - // //print("User ID: $recordId"); - - //String collectionId = userDetailsResponse.data['collectionId'] ?? '_pb_users_auth_'; - - // Ensure recordId and avatarFilename are valid - // if (avatarFilename.isNotEmpty && recordId.isNotEmpty) { - // print("PROFILE AVAILABLE"); - // String _avatarUrl = 'https://pb.venbait.in/api/files/$collectionId/$recordId/$avatarFilename'; - // print("PROFILE AVAILABLE1- $_avatarUrl"); - // //_avatarUrl = File(imageUrl); // This won't work directly for a URL, you need to download the image first - // } else { - // print("PROFILE NOT AVAILABLE"); - // } }); } catch (e) { print('Error fetching user details: $e'); @@ -672,12 +652,8 @@ class _EditProfileState extends State { Center( child: GestureDetector( onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - Changepassword(userId: userId)), - ); + final email = _emailController.text; + context.go('/createNewPw/$userId/$email'); }, child: Text( "Change Password", @@ -691,16 +667,6 @@ class _EditProfileState extends State { ElevatedButton.icon( onPressed: () { showConfirmationDialog(context); - - // if (_formKey.currentState?.validate() ?? false){ - // _formKey.currentState?.save(); - // showConfirmationDialog(context); - // } else { - // setState(() { - // showError = - // !isChecked; // Show error if the checkbox is not checked - // }); - // } }, icon: Icon( Icons.save, diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/manage_users.dart b/lib/presentation/routes/drawer_routes/Drawer Items/manage_users.dart index 7d87d47a..79c919c0 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/manage_users.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/manage_users.dart @@ -92,9 +92,11 @@ class _ManageUserRouterState extends State { filteredUserData.sort((a, b) { final aValue = getField(a); final bValue = getField(b); - if (aValue == null || bValue == null) { - return 0; // Handle null-safe sorting if needed - } + // Handle null values + if (aValue == null && bValue == null) return 0; + if (aValue == null) return ascending ? -1 : 1; + if (bValue == null) return ascending ? 1 : -1; + // Compare values return ascending ? Comparable.compare(aValue, bValue) : Comparable.compare(bValue, aValue); @@ -248,20 +250,24 @@ class _ManageUserRouterState extends State { columns: [ DataColumn( label: const Text('User Name'), - onSort: (columnIndex, ascending) => _sort( - (user) => user.userName, columnIndex, ascending), + onSort: (columnIndex, ascending) { + _sort((user) => user.userName.toLowerCase(), + columnIndex, ascending); + }, ), DataColumn( label: const Text('Email ID'), - onSort: (columnIndex, ascending) => _sort( - (user) => user.emailId, columnIndex, ascending), + onSort: (columnIndex, ascending) { + _sort((user) => user.emailId.toLowerCase(), + columnIndex, ascending); + }, ), DataColumn( label: const Text('Registration Date'), onSort: (columnIndex, ascending) { _sort( - (user) => DateTime.tryParse( - user.registrationDate), // Convert to DateTime + (user) => DateFormat('dd/MM/yyyy') + .parse(user.registrationDate), columnIndex, ascending, ); @@ -269,8 +275,10 @@ class _ManageUserRouterState extends State { ), DataColumn( label: const Text('Status'), - onSort: (columnIndex, ascending) => _sort( - (user) => user.status, columnIndex, ascending), + onSort: (columnIndex, ascending) { + _sort((user) => user.status.toLowerCase(), + columnIndex, ascending); + }, ), ], rows: filteredUserData.isEmpty