diff --git a/lib/data/services/auth_service.dart b/lib/data/services/auth_service.dart index 7a3d5e9..7c762f9 100644 --- a/lib/data/services/auth_service.dart +++ b/lib/data/services/auth_service.dart @@ -44,6 +44,7 @@ class AuthService { if (result is Ok) { final prefs = await SharedPreferences.getInstance(); final token = result.value.data.accessToken; + AppLogger().info('🔑 Bearer token: $token'); final refreshToken = result.value.data.refreshToken; await prefs.setString('bearer', token!); diff --git a/lib/view_models/auth_view_model.dart b/lib/view_models/auth_view_model.dart index 7601d44..421e7d1 100644 --- a/lib/view_models/auth_view_model.dart +++ b/lib/view_models/auth_view_model.dart @@ -1,5 +1,3 @@ -import 'dart:developer'; - import 'package:flutter/material.dart'; import 'package:tm_app/data/services/model/customer/customer.dart'; import 'package:tm_app/data/services/model/forgot_password_response/forgot_password_response_model.dart'; @@ -49,13 +47,16 @@ class AuthViewModel with ChangeNotifier { bool _isResetPasswordLoading = false; String? _errorMessage; String? _resetPasswordErrorMessage; + bool _resetPasswordSuccess = false; Customer? _loggedInUser; String? successMessage; + String? _successMessageOtp; String? _forgotPasswordMessage; bool _isLoadingForgot = false; bool _isLoadingOtp = false; String? _errorMessageForget; String? _errorMessageOtp; + bool _verifyOtpSuccess = false; bool get isLoading => _isLoading; bool get isLoadingForgot => _isLoadingForgot; @@ -65,10 +66,12 @@ class AuthViewModel with ChangeNotifier { String? get errorMessageForget => _errorMessageForget; String? get resetPasswordErrorMessage => _resetPasswordErrorMessage; String? get errorMessageOtp => _errorMessageOtp; - + String? get successMessageOtp => _successMessageOtp; + bool get verifyOtpSuccess => _verifyOtpSuccess; Customer? get loggedInUser => _loggedInUser; String? get forgotPasswordMessage => _forgotPasswordMessage; String? resetToken; + bool get resetPasswordSuccess => _resetPasswordSuccess; void togglePasswordVisibility() { _obscurePassword = !_obscurePassword; @@ -88,7 +91,7 @@ class AuthViewModel with ChangeNotifier { bool get isOtpValid { final otp = otpController.text.trim(); - return otp.length == 5; + return otp.length == 6; } bool get canSubmitOtpPage { @@ -182,8 +185,8 @@ class AuthViewModel with ChangeNotifier { case Ok(): _isLoadingForgot = false; successMessage = result.value.message; + notifyListeners(); - log('$successMessage'); break; @@ -208,6 +211,7 @@ class AuthViewModel with ChangeNotifier { Future verifyOtp(BuildContext context) async { _isLoadingOtp = true; _errorMessageOtp = null; + _verifyOtpSuccess = false; notifyListeners(); final result = await _authRepository.verifyOtp( @@ -219,39 +223,77 @@ class AuthViewModel with ChangeNotifier { case Ok(): _isLoadingOtp = false; resetToken = result.value.data.token; + _successMessageOtp = result.value.message; + _verifyOtpSuccess = true; + notifyListeners(); + break; case Error(): _isLoadingOtp = false; _errorMessageOtp = result.error.toString(); + _verifyOtpSuccess = false; + notifyListeners(); break; } - notifyListeners(); - _errorMessageOtp = null; + _successMessageOtp = null; + notifyListeners(); + } + + void clearOtpFlow() { + _errorMessageOtp = null; + _successMessageOtp = null; + _isLoadingOtp = false; + _verifyOtpSuccess = false; + // Intentionally do not clear resetToken + otpController.clear(); notifyListeners(); } Future resetPassword() async { _isResetPasswordLoading = true; _resetPasswordErrorMessage = null; + _resetPasswordSuccess = false; notifyListeners(); - - final result = await _authRepository.resetPassword( - newPassword: newPasswordController.text.trim(), - token: '', - ); - switch (result) { - case Ok(): - break; - case Error(): - _resetPasswordErrorMessage = result.error.toString(); - break; + if (resetToken != null) { + final result = await _authRepository.resetPassword( + newPassword: newPasswordController.text.trim(), + token: resetToken!, + ); + switch (result) { + case Ok(): + _resetPasswordSuccess = true; + break; + case Error(): + _resetPasswordErrorMessage = result.error.toString(); + _resetPasswordSuccess = false; + break; + } + _isResetPasswordLoading = false; + notifyListeners(); + _resetPasswordErrorMessage = null; + notifyListeners(); + } else { + _resetPasswordErrorMessage = 'Reset token is required'; + _isResetPasswordLoading = false; + notifyListeners(); } - _isResetPasswordLoading = false; + } + + void clearResetPasswordSuccess() { + _resetPasswordSuccess = false; + notifyListeners(); + } + + void clearResetFlow() { _resetPasswordErrorMessage = null; + _resetPasswordSuccess = false; + _isResetPasswordLoading = false; + // Intentionally do not clear resetToken + newPasswordController.clear(); notifyListeners(); } diff --git a/lib/views/forget_password_create/pages/d_forget_password_create_page.dart b/lib/views/forget_password_create/pages/d_forget_password_create_page.dart index d22c3ba..233ae76 100644 --- a/lib/views/forget_password_create/pages/d_forget_password_create_page.dart +++ b/lib/views/forget_password_create/pages/d_forget_password_create_page.dart @@ -6,6 +6,7 @@ import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/views/forget_password_create/strings/forgot_password_create_strings.dart'; import '../../../core/constants/assets.dart'; +import '../../../core/routes/app_routes.dart'; import '../../../core/theme/colors.dart'; import '../../../core/utils/app_toast.dart'; import '../../../view_models/auth_view_model.dart'; @@ -40,6 +41,10 @@ class _DesktopForgotPasswordCreatePageState void _viewModelListener() { if (viewModel.resetPasswordErrorMessage != null) { AppToast.error(context, viewModel.resetPasswordErrorMessage!); + } else if (viewModel.isResetPasswordLoading == false && + viewModel.resetPasswordSuccess) { + Router.neglect(context, () => const LoginScreenRoute().go(context)); + viewModel.clearResetFlow(); } } @@ -146,10 +151,11 @@ class _DesktopForgotPasswordCreatePageState ), SizedBox(height: 80.0.h()), viewModel.isResetPasswordLoading - ? const BaseButton( - isEnabled: false, + ? BaseButton( + isEnabled: viewModel.isNewPasswordValid, text: ForgotPasswordCreateStrings.resetLink, onPressed: null, + isLoading: true, ) : BaseButton( isEnabled: viewModel.isNewPasswordValid, diff --git a/lib/views/forget_password_create/pages/m_forget_password_create_page.dart b/lib/views/forget_password_create/pages/m_forget_password_create_page.dart index 339c6a2..dc01d8e 100644 --- a/lib/views/forget_password_create/pages/m_forget_password_create_page.dart +++ b/lib/views/forget_password_create/pages/m_forget_password_create_page.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; +import 'package:tm_app/core/routes/app_routes.dart'; import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/views/forget_password_create/strings/forgot_password_create_strings.dart'; @@ -40,6 +41,10 @@ class _MobileForgotPasswordCreatePageState void _viewModelListener() { if (viewModel.resetPasswordErrorMessage != null) { AppToast.error(context, viewModel.resetPasswordErrorMessage!); + } else if (viewModel.isResetPasswordLoading == false && + viewModel.resetPasswordSuccess) { + Router.neglect(context, () => const LoginScreenRoute().go(context)); + viewModel.clearResetFlow(); } } @@ -143,10 +148,11 @@ class _MobileForgotPasswordCreatePageState ), SizedBox(height: 80.0.h()), viewModel.isResetPasswordLoading - ? const BaseButton( - isEnabled: false, + ? BaseButton( + isEnabled: viewModel.isNewPasswordValid, text: ForgotPasswordCreateStrings.resetLink, onPressed: null, + isLoading: true, ) : BaseButton( isEnabled: viewModel.isNewPasswordValid, diff --git a/lib/views/forget_password_create/pages/t_forget_password_create_page.dart b/lib/views/forget_password_create/pages/t_forget_password_create_page.dart index fbb189f..5c8f285 100644 --- a/lib/views/forget_password_create/pages/t_forget_password_create_page.dart +++ b/lib/views/forget_password_create/pages/t_forget_password_create_page.dart @@ -6,6 +6,7 @@ import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/views/forget_password_create/strings/forgot_password_create_strings.dart'; import '../../../core/constants/assets.dart'; +import '../../../core/routes/app_routes.dart'; import '../../../core/theme/colors.dart'; import '../../../core/utils/app_toast.dart'; import '../../../view_models/auth_view_model.dart'; @@ -40,6 +41,10 @@ class _TabletForgotPasswordCreatePageState void _viewModelListener() { if (viewModel.resetPasswordErrorMessage != null) { AppToast.error(context, viewModel.resetPasswordErrorMessage!); + } else if (viewModel.isResetPasswordLoading == false && + viewModel.resetPasswordSuccess) { + Router.neglect(context, () => const LoginScreenRoute().go(context)); + viewModel.clearResetFlow(); } } @@ -146,14 +151,16 @@ class _TabletForgotPasswordCreatePageState ), SizedBox(height: 80.0.h()), viewModel.isResetPasswordLoading - ? const BaseButton( - isEnabled: false, + ? BaseButton( + isEnabled: viewModel.isNewPasswordValid, text: ForgotPasswordCreateStrings.resetLink, onPressed: null, + isLoading: true, ) : BaseButton( isEnabled: viewModel.isNewPasswordValid, text: ForgotPasswordCreateStrings.resetLink, + onPressed: viewModel.isNewPasswordValid ? () async { diff --git a/lib/views/forget_password_otp.dart/pages/d_forget_password_otp.dart_page.dart b/lib/views/forget_password_otp.dart/pages/d_forget_password_otp.dart_page.dart index 56d38c2..6ad5b59 100644 --- a/lib/views/forget_password_otp.dart/pages/d_forget_password_otp.dart_page.dart +++ b/lib/views/forget_password_otp.dart/pages/d_forget_password_otp.dart_page.dart @@ -6,12 +6,12 @@ import 'package:provider/provider.dart'; import 'package:tm_app/core/utils/size_config.dart'; import '../../../core/constants/assets.dart'; +import '../../../core/routes/app_routes.dart'; import '../../../core/theme/colors.dart'; import '../../../core/utils/app_toast.dart'; import '../../../view_models/auth_view_model.dart'; import '../../shared/base_button.dart'; import '../strings/forgot_password_otp_strings.dart'; -import '../../forget_password_create/pages/forgot_password_create_screen.dart'; class DesktopForgotPasswordOtpPage extends StatefulWidget { const DesktopForgotPasswordOtpPage({super.key}); @@ -35,16 +35,14 @@ class _DesktopForgotPasswordOtpPageState void _viewModelListener() { if (viewModel.errorMessageOtp != null) { AppToast.error(context, viewModel.errorMessageOtp!); - } - - if (viewModel.resetToken != null) { + } else if (!viewModel.isLoadingOtp && + viewModel.verifyOtpSuccess && + viewModel.resetToken != null) { Router.neglect( context, - () => Navigator.push( - context, - MaterialPageRoute(builder: (_) => const ForgotPasswordCreateScreen()), - ), + () => const ForgotPasswordCreateRouteData().pushReplacement(context), ); + viewModel.clearOtpFlow(); } } @@ -99,7 +97,7 @@ class _DesktopForgotPasswordOtpPageState SizedBox(height: 40.0.h()), Pinput( controller: viewModel.otpController, - length: 5, + length: 6, pinAnimationType: PinAnimationType.scale, defaultPinTheme: PinTheme( width: 50.0.w(), @@ -117,20 +115,22 @@ class _DesktopForgotPasswordOtpPageState ), SizedBox(height: 24.0.h()), viewModel.isLoadingOtp - ? const BaseButton( - isEnabled: false, - text: ForgotPasswordOtpStrings.resetLink, - onPressed: null, - ) + ? BaseButton( + isEnabled: viewModel.isOtpValid, + text: ForgotPasswordOtpStrings.resetLink, + onPressed: null, + isLoading: true, + ) : BaseButton( - isEnabled: viewModel.canSubmitOtpPage, - text: ForgotPasswordOtpStrings.resetLink, - onPressed: viewModel.canSubmitOtpPage - ? () async { + isEnabled: viewModel.canSubmitOtpPage, + text: ForgotPasswordOtpStrings.resetLink, + onPressed: + viewModel.canSubmitOtpPage + ? () async { await viewModel.verifyOtp(context); } - : null, - ), + : null, + ), const SizedBox(height: 12.0), TextButton( onPressed: () { diff --git a/lib/views/forget_password_otp.dart/pages/m_forget_password_otp.dart_page.dart b/lib/views/forget_password_otp.dart/pages/m_forget_password_otp.dart_page.dart index cff1604..fc29c89 100644 --- a/lib/views/forget_password_otp.dart/pages/m_forget_password_otp.dart_page.dart +++ b/lib/views/forget_password_otp.dart/pages/m_forget_password_otp.dart_page.dart @@ -35,10 +35,11 @@ class _MobileForgotPasswordOtpPageState void _viewModelListener() { if (viewModel.errorMessageOtp != null) { AppToast.error(context, viewModel.errorMessageOtp!); - } - - if (viewModel.resetToken != null) { - const ForgotPasswordCreateRouteData().push(context); + } else if (!viewModel.isLoadingOtp && + viewModel.verifyOtpSuccess && + viewModel.resetToken != null) { + const ForgotPasswordCreateRouteData().pushReplacement(context); + viewModel.clearOtpFlow(); } } @@ -90,7 +91,7 @@ class _MobileForgotPasswordOtpPageState SizedBox(height: 40.0.h()), Pinput( controller: viewModel.otpController, - length: 5, + length: 6, pinAnimationType: PinAnimationType.scale, defaultPinTheme: PinTheme( width: 50.0.w(), @@ -108,10 +109,11 @@ class _MobileForgotPasswordOtpPageState ), SizedBox(height: 24.0.h()), viewModel.isLoadingOtp - ? const BaseButton( - isEnabled: false, + ? BaseButton( + isEnabled: viewModel.isOtpValid, text: ForgotPasswordOtpStrings.resetLink, onPressed: null, + isLoading: true, ) : BaseButton( isEnabled: viewModel.canSubmitOtpPage, diff --git a/lib/views/forget_password_otp.dart/pages/t_forget_password_otp.dart_page.dart b/lib/views/forget_password_otp.dart/pages/t_forget_password_otp.dart_page.dart index 529dff5..22bddd8 100644 --- a/lib/views/forget_password_otp.dart/pages/t_forget_password_otp.dart_page.dart +++ b/lib/views/forget_password_otp.dart/pages/t_forget_password_otp.dart_page.dart @@ -6,12 +6,12 @@ import 'package:provider/provider.dart'; import 'package:tm_app/core/utils/size_config.dart'; import '../../../core/constants/assets.dart'; +import '../../../core/routes/app_routes.dart'; import '../../../core/theme/colors.dart'; import '../../../core/utils/app_toast.dart'; import '../../../view_models/auth_view_model.dart'; import '../../shared/base_button.dart'; import '../strings/forgot_password_otp_strings.dart'; -import '../../forget_password_create/pages/forgot_password_create_screen.dart'; class TabletForgotPasswordOtpPage extends StatefulWidget { const TabletForgotPasswordOtpPage({super.key}); @@ -35,16 +35,14 @@ class _TabletForgotPasswordOtpPageState void _viewModelListener() { if (viewModel.errorMessageOtp != null) { AppToast.error(context, viewModel.errorMessageOtp!); - } - - if (viewModel.resetToken != null) { + } else if (!viewModel.isLoadingOtp && + viewModel.verifyOtpSuccess && + viewModel.resetToken != null) { Router.neglect( context, - () => Navigator.push( - context, - MaterialPageRoute(builder: (_) => const ForgotPasswordCreateScreen()), - ), + () => const ForgotPasswordCreateRouteData().pushReplacement(context), ); + viewModel.clearOtpFlow(); } } @@ -99,7 +97,7 @@ class _TabletForgotPasswordOtpPageState SizedBox(height: 40.0.h()), Pinput( controller: viewModel.otpController, - length: 5, + length: 6, pinAnimationType: PinAnimationType.scale, defaultPinTheme: PinTheme( width: 50.0.w(), @@ -117,20 +115,22 @@ class _TabletForgotPasswordOtpPageState ), SizedBox(height: 24.0.h()), viewModel.isLoadingOtp - ? const BaseButton( - isEnabled: false, - text: ForgotPasswordOtpStrings.resetLink, - onPressed: null, - ) + ? BaseButton( + isEnabled: viewModel.isOtpValid, + text: ForgotPasswordOtpStrings.resetLink, + onPressed: null, + isLoading: true, + ) : BaseButton( - isEnabled: viewModel.canSubmitOtpPage, - text: ForgotPasswordOtpStrings.resetLink, - onPressed: viewModel.canSubmitOtpPage - ? () async { + isEnabled: viewModel.canSubmitOtpPage, + text: ForgotPasswordOtpStrings.resetLink, + onPressed: + viewModel.canSubmitOtpPage + ? () async { await viewModel.verifyOtp(context); } - : null, - ), + : null, + ), const SizedBox(height: 12.0), TextButton( onPressed: () { diff --git a/lib/views/forgot_password/pages/d_forgot_apssword_page.dart b/lib/views/forgot_password/pages/d_forgot_apssword_page.dart index 69c2c05..e0291c3 100644 --- a/lib/views/forgot_password/pages/d_forgot_apssword_page.dart +++ b/lib/views/forgot_password/pages/d_forgot_apssword_page.dart @@ -33,7 +33,10 @@ class _DesktopForgotPasswordPageState extends State { void _viewModelListener() { if (viewModel.successMessage != null) { AppToast.success(context, viewModel.successMessage!); - const ForgotPasswordOtpRouteData().push(context); + Router.neglect( + context, + () => const ForgotPasswordOtpRouteData().pushReplacement(context), + ); viewModel.clearMessages(); } @@ -103,19 +106,21 @@ class _DesktopForgotPasswordPageState extends State { SizedBox(height: 70.0.h()), viewModel.isLoadingForgot ? const BaseButton( - isEnabled: false, - text: ForgotPasswordStrings.resetLink, - onPressed: null, - ) + isEnabled: false, + text: ForgotPasswordStrings.resetLink, + onPressed: null, + isLoading: true, + ) : BaseButton( - isEnabled: viewModel.isEmailValid, - text: ForgotPasswordStrings.resetLink, - onPressed: viewModel.isEmailValid - ? () async { + isEnabled: viewModel.isEmailValid, + text: ForgotPasswordStrings.resetLink, + onPressed: + viewModel.isEmailValid + ? () async { await viewModel.forgotPassword(context); } - : null, - ), + : null, + ), const SizedBox(height: 12), TextButton( onPressed: () { diff --git a/lib/views/forgot_password/pages/m_forgot_password_page.dart b/lib/views/forgot_password/pages/m_forgot_password_page.dart index b69a41e..b39ce22 100644 --- a/lib/views/forgot_password/pages/m_forgot_password_page.dart +++ b/lib/views/forgot_password/pages/m_forgot_password_page.dart @@ -33,7 +33,7 @@ class _MobileForgotPasswordPageState extends State { void _viewModelListener() { if (viewModel.successMessage != null) { AppToast.success(context, viewModel.successMessage!); - const ForgotPasswordOtpRouteData().push(context); + const ForgotPasswordOtpRouteData().pushReplacement(context); viewModel.clearMessages(); } @@ -98,19 +98,21 @@ class _MobileForgotPasswordPageState extends State { SizedBox(height: 24.0.h()), viewModel.isLoadingForgot ? const BaseButton( - isEnabled: false, - text: ForgotPasswordStrings.resetLink, - onPressed: null, - ) + isEnabled: false, + text: ForgotPasswordStrings.resetLink, + onPressed: null, + isLoading: true, + ) : BaseButton( - isEnabled: viewModel.isEmailValid, - text: ForgotPasswordStrings.resetLink, - onPressed: viewModel.isEmailValid - ? () async { + isEnabled: viewModel.isEmailValid, + text: ForgotPasswordStrings.resetLink, + onPressed: + viewModel.isEmailValid + ? () async { await viewModel.forgotPassword(context); } - : null, - ), + : null, + ), TextButton( onPressed: () { Navigator.pop(context); diff --git a/lib/views/forgot_password/pages/t_forgot_password_page.dart b/lib/views/forgot_password/pages/t_forgot_password_page.dart index 420f552..cbae08e 100644 --- a/lib/views/forgot_password/pages/t_forgot_password_page.dart +++ b/lib/views/forgot_password/pages/t_forgot_password_page.dart @@ -33,7 +33,10 @@ class _TabletForgotPasswordPageState extends State { void _viewModelListener() { if (viewModel.successMessage != null) { AppToast.success(context, viewModel.successMessage!); - const ForgotPasswordOtpRouteData().push(context); + Router.neglect( + context, + () => const ForgotPasswordOtpRouteData().pushReplacement(context), + ); viewModel.clearMessages(); } @@ -97,19 +100,21 @@ class _TabletForgotPasswordPageState extends State { SizedBox(height: 24.0.h()), viewModel.isLoadingForgot ? const BaseButton( - isEnabled: false, - text: ForgotPasswordStrings.resetLink, - onPressed: null, - ) + isEnabled: false, + text: ForgotPasswordStrings.resetLink, + onPressed: null, + isLoading: true, + ) : BaseButton( - isEnabled: viewModel.isEmailValid, - text: ForgotPasswordStrings.resetLink, - onPressed: viewModel.isEmailValid - ? () async { + isEnabled: viewModel.isEmailValid, + text: ForgotPasswordStrings.resetLink, + onPressed: + viewModel.isEmailValid + ? () async { await viewModel.forgotPassword(context); } - : null, - ), + : null, + ), const SizedBox(height: 12), TextButton( onPressed: () { diff --git a/lib/views/login/pages/login_desktop_page.dart b/lib/views/login/pages/login_desktop_page.dart index 58f602e..b4fc9b4 100644 --- a/lib/views/login/pages/login_desktop_page.dart +++ b/lib/views/login/pages/login_desktop_page.dart @@ -106,20 +106,20 @@ class _LoginDesktopPageState extends State { } }, ), - // SizedBox(height: 32.0), - // TextButton( - // onPressed: () { - // ForgotPasswordRouteData().push(context); - // }, - // child: Text( - // LoginStrings.forgotPassword, - // style: TextStyle( - // fontSize: 14.0.sp(), - // fontWeight: FontWeight.w500, - // color: AppColors.mainBlue, - // ), - // ), - // ), + const SizedBox(height: 32.0), + TextButton( + onPressed: () { + const ForgotPasswordRouteData().push(context); + }, + child: Text( + LoginStrings.forgotPassword, + style: TextStyle( + fontSize: 14.0.sp(), + fontWeight: FontWeight.w500, + color: AppColors.mainBlue, + ), + ), + ), ], ); }, diff --git a/lib/views/login/pages/login_tablet_page.dart b/lib/views/login/pages/login_tablet_page.dart index 55475cc..5886430 100644 --- a/lib/views/login/pages/login_tablet_page.dart +++ b/lib/views/login/pages/login_tablet_page.dart @@ -99,20 +99,20 @@ class _LoginTabletPageState extends State { } : null, ), - // SizedBox(height: 32.0), - // TextButton( - // onPressed: () { - // ForgotPasswordRouteData().push(context); - // }, - // child: Text( - // LoginStrings.forgotPassword, - // style: TextStyle( - // fontSize: 14.0.sp(), - // fontWeight: FontWeight.w500, - // color: AppColors.mainBlue, - // ), - // ), - // ), + const SizedBox(height: 32.0), + TextButton( + onPressed: () { + const ForgotPasswordRouteData().push(context); + }, + child: Text( + LoginStrings.forgotPassword, + style: TextStyle( + fontSize: 14.0.sp(), + fontWeight: FontWeight.w500, + color: AppColors.mainBlue, + ), + ), + ), ], ); },