From 3beae201804e19345f1639d4a597b49c2cd0a574 Mon Sep 17 00:00:00 2001 From: venbaittech Date: Sun, 24 Nov 2024 11:25:46 +0530 Subject: [PATCH] change password functionality added --- lib/presentation/Screens/changepassword.dart | 161 +++++++++++++++--- .../Screens/confirm_password.dart | 143 +++++++++++++--- .../Screens/otp_verification.dart | 153 +++++++++++++---- lib/presentation/Screens/profilepage.dart | 35 ++-- lib/presentation/Screens/registration.dart | 22 ++- .../routes/drawer_routes/feedback_route.dart | 3 +- 6 files changed, 410 insertions(+), 107 deletions(-) diff --git a/lib/presentation/Screens/changepassword.dart b/lib/presentation/Screens/changepassword.dart index f4d4c18e..8c0155e0 100644 --- a/lib/presentation/Screens/changepassword.dart +++ b/lib/presentation/Screens/changepassword.dart @@ -1,5 +1,7 @@ - +import 'dart:convert'; +import 'package:http/http.dart' as http; import 'package:flutter/material.dart'; +import 'package:pocketbase/pocketbase.dart'; import 'otp_verification.dart'; class Changepassword extends StatefulWidget { @@ -10,22 +12,118 @@ class Changepassword extends StatefulWidget { } class _ResetPasswordScreenState extends State { + final _pb = PocketBase('https://pb.venbait.in'); + // final pb = PocketBase('http://127.0.0.1:8090'); final _formKey = GlobalKey(); final _emailController = TextEditingController(); bool _isLoading = false; + String? _otpId; - void _handleSubmit() { + String? _verificationCode; + Future sendVerificationCode(String email) async { if (_formKey.currentState!.validate()) { setState(() { _isLoading = true; }); - Navigator.push( - context, - MaterialPageRoute(builder: (context) => EmailVerificationScreen()), - ); - // TODO: Implement your password reset logic here - // After API call, set _isLoading back to false + final email = _emailController.text.trim(); + + //print('Email - $email'); + + try { + // Authenticate admin to get the token + final adminAuth = await _pb.admins + .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); + final authToken = adminAuth.token; + + //print('Admin Token: $authToken'); + + // Check if email exists in the user collection + final userCheckResponse = await http.get( + Uri.parse( + 'https://pb.venbait.in/api/collections/users/records?filter=email="$email"'), + headers: { + 'Authorization': 'Bearer $authToken', + }, + ); + + if (userCheckResponse.statusCode == 200) { + final userData = jsonDecode(userCheckResponse.body); + + //print('userDetails ->: $userData'); + if (userData['items'].isNotEmpty) { + // Email exists; retrieve user ID + final userId = userData['items'][0]['id']; + + // Proceed with OTP request + final otpResponse = await http.post( + Uri.parse( + 'https://pb.venbait.in/api/collections/otp_requests/records'), + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $authToken', + }, + body: jsonEncode({ + 'email': email, + }), + ); + + if (otpResponse.statusCode == 200) { + //print('otpResponse- ${otpResponse.body} '); + + final otpData = jsonDecode(otpResponse.body); + final otp = otpData['otp']; // OTP value from the response + final otpId = otpData['id']; + _otpId = otpData['id']; + //print('OTP: $otp, ID: $otpId, '); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Verification code sent to $email")), + ); + + // Navigate to the Email Verification screen + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => EmailVerificationScreen( + email: email, + userId: userId, // Pass user ID to the next screen + otp: otp, + otpId: otpId, + sendVerificationCode: sendVerificationCode, + ), + ), + ); + } else { + final error = + jsonDecode(otpResponse.body)['error'] ?? "Unknown error"; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Error: $error")), + ); + } + } else { + // Email not found + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Please Enter Registered Mail ID")), + ); + } + } else { + // Error in user collection request + final error = + jsonDecode(userCheckResponse.body)['error'] ?? "Unknown error"; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Error: $error")), + ); + } + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Failed to process request: $e")), + ); + } finally { + setState(() { + _isLoading = false; + }); + } } } @@ -134,7 +232,12 @@ class _ResetPasswordScreenState extends State { width: double.infinity, height: 48, child: ElevatedButton( - onPressed: _isLoading ? null : _handleSubmit, + onPressed: _isLoading + ? null + : () { + final email = _emailController.text.trim(); + sendVerificationCode(email); + }, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF8B7355), foregroundColor: Colors.white, @@ -163,9 +266,14 @@ class _ResetPasswordScreenState extends State { fontWeight: FontWeight.w500, ), ), - SizedBox(width: 2,), - Icon(Icons.arrow_forward_ios,color: Colors.white38,size: 18,), - + SizedBox( + width: 2, + ), + Icon( + Icons.arrow_forward_ios, + color: Colors.white38, + size: 18, + ), ], ], ), @@ -173,19 +281,21 @@ class _ResetPasswordScreenState extends State { ), ], ), - SizedBox(height: screenheight/3,), - + SizedBox( + height: screenheight / 3, + ), Center( child: Container( - height: screenheight/8, - width: screenwidth/2, - decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage("assets/splash_screen/logo.png"), // Background image asset - fit: BoxFit.fill, - ), - ), - )) + height: screenheight / 8, + width: screenwidth / 2, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "assets/splash_screen/logo.png"), // Background image asset + fit: BoxFit.fill, + ), + ), + )) ], ), ), @@ -195,8 +305,3 @@ class _ResetPasswordScreenState extends State { ); } } - - - - - diff --git a/lib/presentation/Screens/confirm_password.dart b/lib/presentation/Screens/confirm_password.dart index 0edd45c6..6bb6b26d 100644 --- a/lib/presentation/Screens/confirm_password.dart +++ b/lib/presentation/Screens/confirm_password.dart @@ -1,13 +1,19 @@ import 'package:flutter/material.dart'; +import 'package:pocketbase/pocketbase.dart'; +import 'package:uae_stat/presentation/Screens/profilepage.dart'; class ConfirmPassword extends StatefulWidget { - ConfirmPassword({super.key}); + final String email; + final String userId; + ConfirmPassword({required this.email, required this.userId}); @override State createState() => _ConfirmPasswordState(); } class _ConfirmPasswordState extends State { + final _pb = PocketBase('https://pb.venbait.in'); + // final pb = PocketBase('http://127.0.0.1:8090'); final _formKey = GlobalKey(); bool _obscurePassword = true; bool _obscureConfirmPassword = true; @@ -32,6 +38,68 @@ class _ConfirmPasswordState extends State { return null; } + // Function to check and update password + Future updatePassword(String userId, String newPassword) async { + try { + //print('userRecord- $userId'); + + // Authenticate as admin + + final adminAuth = await _pb.admins + .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); + final token = adminAuth.token; + + final headers = { + 'Authorization': 'Bearer $token', + }; + + final userRecord = await _pb.collection('users').getOne(userId); + //print('userRecord- $userRecord'); + final oldPassword = userRecord.data['password']; + print(oldPassword); + + // Check if the new password is the same as the old password + if (oldPassword == newPassword) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'New password is similar to the old password. Please try a different one.'), + backgroundColor: Colors.orange, + ), + ); + return; // Exit early + } + + // Update password + await _pb.collection('users').update( + userId, // User ID + body: {'password': newPassword}, // Updated password + ); + + // Show success Snackbar + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Password updated successfully'), + backgroundColor: Colors.blue, + ), + ); + + Navigator.push( + context, + MaterialPageRoute(builder: (context) => ProfileScreen(userId: userId)), + ); + } catch (e) { + print('Error updating password: $e'); + // Show error Snackbar + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Failed to update password'), + backgroundColor: Colors.red, + ), + ); + } + } + @override Widget build(BuildContext context) { double screenheight = MediaQuery.of(context).size.height; @@ -49,7 +117,9 @@ class _ConfirmPasswordState extends State { padding: const EdgeInsets.all(24.0), child: Column( children: [ - SizedBox(height: screenheight/6,), + SizedBox( + height: screenheight / 6, + ), Text( "Create New Password", style: TextStyle( @@ -57,7 +127,9 @@ class _ConfirmPasswordState extends State { fontWeight: FontWeight.bold, ), ), - SizedBox(height: screenheight/35,), + SizedBox( + height: screenheight / 35, + ), Text( "Your new password must de different\nform previously used password", textAlign: TextAlign.center, @@ -66,7 +138,9 @@ class _ConfirmPasswordState extends State { color: Colors.grey[600], ), ), - SizedBox(height: screenheight/35,), + SizedBox( + height: screenheight / 35, + ), TextFormField( obscureText: _obscurePassword, decoration: InputDecoration( @@ -92,7 +166,9 @@ class _ConfirmPasswordState extends State { ), validator: _validatePassword, ), - SizedBox(height: screenheight/35,), + SizedBox( + height: screenheight / 35, + ), TextFormField( obscureText: _obscureConfirmPassword, decoration: InputDecoration( @@ -110,7 +186,8 @@ class _ConfirmPasswordState extends State { ), onPressed: () { setState(() { - _obscureConfirmPassword = !_obscureConfirmPassword; + _obscureConfirmPassword = + !_obscureConfirmPassword; }); }, ), @@ -118,20 +195,28 @@ class _ConfirmPasswordState extends State { ), validator: _validateConfirmPassword, ), - SizedBox(height: screenheight/35,), SizedBox( - width: screenwidth/1.1, + height: screenheight / 35, + ), + SizedBox( + width: screenwidth / 1.1, child: ElevatedButton( - onPressed: () { + onPressed: () async { if ((_formKey.currentState?.validate() ?? false)) { - // Proceed with registration if form is valid and checkbox is checked + //String userId = '4hai9cbn4lg6jt4'; // Replace with the actual user ID + String newPassword = _password ?? + ''; // Replace with the new password + //print("Confirm Passwd- $widget.userId, $newPassword"); + + await updatePassword(widget.userId, newPassword); } // Add verification logic here }, style: ElevatedButton.styleFrom( backgroundColor: Colors.brown[300], - padding: EdgeInsets.symmetric(horizontal: 80, vertical: 16), + padding: EdgeInsets.symmetric( + horizontal: 80, vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), @@ -141,10 +226,17 @@ class _ConfirmPasswordState extends State { children: [ Text( "Save", - style: TextStyle(fontSize: 18,color: Colors.white), + style: TextStyle( + fontSize: 18, color: Colors.white), + ), + SizedBox( + width: 2, + ), + Icon( + Icons.arrow_forward_ios, + color: Colors.white38, + size: 18, ), - SizedBox(width: 2,), - Icon(Icons.arrow_forward_ios,color: Colors.white38,size: 18,), ], ), ), @@ -152,18 +244,21 @@ class _ConfirmPasswordState extends State { ], ), ), - SizedBox(height: screenheight/15,), + SizedBox( + height: screenheight / 15, + ), Center( child: Container( - height: screenheight/10, - width: screenwidth/2.5, - decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage("assets/splash_screen/logo.png"), // Background image asset - fit: BoxFit.fill, - ), - ), - )) + height: screenheight / 10, + width: screenwidth / 2.5, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "assets/splash_screen/logo.png"), // Background image asset + fit: BoxFit.fill, + ), + ), + )) ], ), ), diff --git a/lib/presentation/Screens/otp_verification.dart b/lib/presentation/Screens/otp_verification.dart index e2fddc2b..f5c4c6c8 100644 --- a/lib/presentation/Screens/otp_verification.dart +++ b/lib/presentation/Screens/otp_verification.dart @@ -1,20 +1,35 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:pocketbase/pocketbase.dart'; import 'confirm_password.dart'; class EmailVerificationScreen extends StatefulWidget { + final String email; + final String userId, otp, otpId; + final Function(String) sendVerificationCode; + + EmailVerificationScreen( + {required this.email, + required this.userId, + required this.otp, + required this.otpId, + required this.sendVerificationCode}); + @override _EmailVerificationScreenState createState() => _EmailVerificationScreenState(); } class _EmailVerificationScreenState extends State { + // final pb = PocketBase('http://127.0.0.1:8090'); + final _pb = PocketBase('https://pb.venbait.in'); final List _otpControllers = - List.generate(4, (_) => TextEditingController()); + List.generate(4, (_) => TextEditingController()); int _secondsRemaining = 120; // 2 minutes timer late Timer _timer; + bool _canResend = false; @override void initState() { @@ -23,12 +38,16 @@ class _EmailVerificationScreenState extends State { } void _startTimer() { + _canResend = false; _timer = Timer.periodic(Duration(seconds: 1), (timer) { if (_secondsRemaining > 0) { setState(() { _secondsRemaining--; }); } else { + setState(() { + _canResend = true; // Enable "Resend Code" when the timer ends + }); timer.cancel(); } }); @@ -49,6 +68,42 @@ class _EmailVerificationScreenState extends State { super.dispose(); } + void verify(BuildContext context, String email, String enteredOTP, + String userId) async { + // Validate OTP length + if (enteredOTP.length != 4) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Please enter a valid 4-digit code!")), + ); + return; + } + + try { + if (enteredOTP == widget.otp && email == widget.email) { + // Parse the response + + //print('VERIFIED OTF'); + + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ConfirmPassword( + email: widget.email, // Pass the email + userId: widget.userId)), + ); + } else { + // No matching record found + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Invalid OTP. Please try again.")), + ); + } + } catch (e) { + // Catch network or other unexpected errors + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Something went wrong. Please try again.")), + ); + } + } @override Widget build(BuildContext context) { @@ -63,7 +118,9 @@ class _EmailVerificationScreenState extends State { children: [ Column( children: [ - SizedBox(height: screenheight/6,), + SizedBox( + height: screenheight / 6, + ), Text( "Verify Your Email", style: TextStyle( @@ -90,11 +147,11 @@ class _EmailVerificationScreenState extends State { height: 50, alignment: Alignment.center, decoration: BoxDecoration( - border: Border.all(color: Colors.grey.shade400, width: 1), + border: + Border.all(color: Colors.grey.shade400, width: 1), borderRadius: BorderRadius.circular(8), ), - child: - TextField( + child: TextField( controller: _otpControllers[index], keyboardType: TextInputType.number, textAlign: TextAlign.center, @@ -103,11 +160,13 @@ class _EmailVerificationScreenState extends State { counterText: "", border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: BorderSide(color: Colors.blue, width: 2), + borderSide: + BorderSide(color: Colors.blue, width: 2), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: BorderSide(color: Colors.blue, width: 2), + borderSide: + BorderSide(color: Colors.blue, width: 2), ), ), onChanged: (value) { @@ -127,36 +186,49 @@ class _EmailVerificationScreenState extends State { children: [ TextSpan( text: _formattedTime, - style: TextStyle(fontWeight: FontWeight.w900,color: Colors.black87), + style: TextStyle( + fontWeight: FontWeight.w900, + color: Colors.black87), ), ], ), ), SizedBox(height: 5), TextButton( - onPressed: () { - setState(() { - _secondsRemaining = 120; - _startTimer(); - }); - }, - child: Text("Resend Code",style: TextStyle(color: Colors.brown,fontWeight: FontWeight.bold),), + onPressed: _canResend + ? () { + setState(() { + _secondsRemaining = 120; + _startTimer(); + }); + widget.sendVerificationCode(widget.email); + } + : null, + child: Text( + "Resend Code", + style: TextStyle( + color: Colors.brown, fontWeight: FontWeight.bold), + ), ), SizedBox(height: 5), SizedBox( - width: screenwidth/1.2, + width: screenwidth / 1.2, child: ElevatedButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => ConfirmPassword()), - ); + onPressed: () async { + // Combine the entered OTP + String enteredOTP = _otpControllers + .map((controller) => controller.text) + .join(); + + verify( + context, widget.email, enteredOTP, widget.userId); // Add verification logic here }, style: ElevatedButton.styleFrom( backgroundColor: Colors.brown[300], - padding: EdgeInsets.symmetric(horizontal: 80, vertical: 16), + padding: + EdgeInsets.symmetric(horizontal: 80, vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), @@ -166,32 +238,41 @@ class _EmailVerificationScreenState extends State { children: [ Text( "Verify", - style: TextStyle(fontSize: 18,color: Colors.white), + style: TextStyle(fontSize: 18, color: Colors.white), + ), + SizedBox( + width: 3, + ), + Icon( + Icons.arrow_forward_ios, + color: Colors.white38, + size: 18, ), - SizedBox(width: 3,), - Icon(Icons.arrow_forward_ios,color: Colors.white38,size: 18,), ], ), ), ), ], ), - SizedBox(height: screenheight/6,), + SizedBox( + height: screenheight / 6, + ), Center( child: Container( - height: screenheight/8, - width: screenwidth/2, - decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage("assets/splash_screen/logo.png"), // Background image asset - fit: BoxFit.fill, - ), - ), - )) + height: screenheight / 8, + width: screenwidth / 2, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "assets/splash_screen/logo.png"), // Background image asset + fit: BoxFit.fill, + ), + ), + )) ], ), ), ), ); } -} \ No newline at end of file +} diff --git a/lib/presentation/Screens/profilepage.dart b/lib/presentation/Screens/profilepage.dart index 56ec11e5..f0ab349f 100644 --- a/lib/presentation/Screens/profilepage.dart +++ b/lib/presentation/Screens/profilepage.dart @@ -172,24 +172,31 @@ class _ProfileScreenState extends State { DateTime dob = DateFormat('dd/MM/yyyy').parse(dateOfBirth); String formattedDob = DateFormat('yyyy-MM-dd').format(dob); - // Prepare the data to be updated - final Map userData = { - 'full_name': fullName, - 'dob': formattedDob, - 'country_region': countryRegion, - 'is_profile_completed': 'True' - }; + // Create a multipart request + final uri = + Uri.parse('${_pb.baseUrl}/api/collections/users/records/$userID'); + final request = http.MultipartRequest('PATCH', uri); - // If there's a profile image, upload it separately or include in userData if PocketBase supports files in the update + // Add other fields + request.fields['full_name'] = fullName; + request.fields['dob'] = formattedDob; + request.fields['country_region'] = countryRegion; + request.fields['is_profile_completed'] = 'True'; + + // Add the file, if available if (_profileImage != null) { - userData['avatar'] = await http.MultipartFile.fromPath( - 'avatar', _profileImage!.path); + request.files.add(await http.MultipartFile.fromPath( + 'avatar', + _profileImage!.path, + )); } - // Update the user profile in PocketBase - final updatedUser = - await _pb.collection('users').update(userID, body: userData); - print(updatedUser); + // Add headers (if required, e.g., authorization) + request.headers['Authorization'] = 'Bearer ${_pb.authStore.token}'; + + // Send the request + final response = await request.send(); + print(response); _resetFormFields(); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Profile updated successfully!"))); diff --git a/lib/presentation/Screens/registration.dart b/lib/presentation/Screens/registration.dart index ccf11118..d87b0562 100644 --- a/lib/presentation/Screens/registration.dart +++ b/lib/presentation/Screens/registration.dart @@ -24,7 +24,9 @@ class _RegisterScreenState extends State { String? _password; bool registrationSuccess = false; bool registrationFailed = false; + dynamic userID; final pb = PocketBase('https://pb.venbait.in'); + // final pb = PocketBase('http://127.0.0.1:8090'); @override void initState() { @@ -80,6 +82,9 @@ class _RegisterScreenState extends State { if (_formKey.currentState?.validate() ?? false) { if (isChecked) { try { + final adminAuth = await pb.admins + .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); + final adminToken = adminAuth.token; // Create user in PocketBase final response = await pb.collection('users').create(body: { 'username': _usernameController.text, @@ -87,12 +92,16 @@ class _RegisterScreenState extends State { 'password': _passwordController.text, 'passwordConfirm': _passwordController .text, // PocketBase requires password confirmation + }, headers: { + 'Authorization': adminToken }); if (response.id != null) { + userID = response.id; // Request email verification - PocketBaseService.users.requestVerification(_emailController.text); + // PocketBaseService.users.requestVerification(_emailController.text); // Show success message instead of navigating away setState(() { + userID = response.id; registrationSuccess = true; registrationFailed = false; // Show success message on success }); @@ -182,9 +191,14 @@ class _RegisterScreenState extends State { ), SizedBox(height: 10), ElevatedButton( - onPressed: () => context.go( - '/${context.language}/login', - ), + onPressed: () => { + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => + ProfileScreen(userId: userID)), + ), + }, child: Text( 'Go to Login', ), diff --git a/lib/presentation/routes/drawer_routes/feedback_route.dart b/lib/presentation/routes/drawer_routes/feedback_route.dart index ec860155..d23f43fa 100644 --- a/lib/presentation/routes/drawer_routes/feedback_route.dart +++ b/lib/presentation/routes/drawer_routes/feedback_route.dart @@ -29,7 +29,8 @@ class _FeedbackFormState extends State with WidgetsBindingObserver { final _pb = PocketBase('https://pb.venbait.in'); // Initialize PocketBase client - // final _pb = PocketBase('http://127.0.0.1:8090'); // Initialize PocketBase client + // final _pb = + // PocketBase('http://127.0.0.1:8090'); // Initialize PocketBase client final TextEditingController _feedbackController = TextEditingController();