diff --git a/lib/data/repositories/auth_repository.dart b/lib/data/repositories/auth_repository.dart index ff0d644..ea3f87f 100644 --- a/lib/data/repositories/auth_repository.dart +++ b/lib/data/repositories/auth_repository.dart @@ -5,6 +5,7 @@ import 'package:tm_app/data/services/model/verify_otp_response/verify_otp_respon import '../../core/utils/result.dart'; import '../services/auth_service.dart'; import '../services/model/logout_response/logout_response.dart'; +import '../services/model/reset_password_response/reset_password_response.dart'; class AuthRepository { final AuthService _authService; @@ -23,6 +24,13 @@ class AuthRepository { return _authService.logout(); } + Future> resetPassword({ + required String newPassword, + required String token, + }) async { + return _authService.resetPassword(newPassword: newPassword, token: token); + } + Future localLogout() async { return _authService.localLogout(); } diff --git a/lib/data/services/api/auth_api.dart b/lib/data/services/api/auth_api.dart index 9fd6b59..fdd8c5a 100644 --- a/lib/data/services/api/auth_api.dart +++ b/lib/data/services/api/auth_api.dart @@ -2,4 +2,5 @@ class AuthApi { static const String login = '/api/v1/profile/login'; static const String logout = '/api/v1/profile/logout'; static const String forgotPassword = '/api/v1/profile/forgot-password'; + static const String resetPassword = '/api/v1/profile/reset-password'; } diff --git a/lib/data/services/auth_service.dart b/lib/data/services/auth_service.dart index 87ca9a5..aa98185 100644 --- a/lib/data/services/auth_service.dart +++ b/lib/data/services/auth_service.dart @@ -8,6 +8,7 @@ import 'package:tm_app/data/services/api/auth_api.dart'; import 'package:tm_app/data/services/model/forgot_password_response/forgot_password_response_model.dart'; import 'package:tm_app/data/services/model/login_response/login_response_model.dart'; import 'package:tm_app/data/services/model/logout_response/logout_response.dart'; +import 'package:tm_app/data/services/model/reset_password_response/reset_password_response.dart'; import 'package:tm_app/data/services/model/verify_otp_response/verify_otp_response_model.dart'; import '../../core/network/network_manager.dart'; @@ -52,6 +53,20 @@ class AuthService { return result; } + Future> resetPassword({ + required String newPassword, + required String token, + }) async { + var data = jsonEncode({"new_password": newPassword, "token": token}); + final result = await _networkManager.makeRequest( + AuthApi.resetPassword, + method: 'POST', + (json) => ResetPasswordResponse.fromJson(json), + data: data, + ); + return result; + } + Future localLogout() async { final prefs = await SharedPreferences.getInstance(); await prefs.remove('bearer'); @@ -75,19 +90,18 @@ class AuthService { } Future> verifyOtp({ - required String code, - required String email, -}) async { - final data = jsonEncode({"code": code, "email": email}); + required String code, + required String email, + }) async { + final data = jsonEncode({"code": code, "email": email}); - final result = await _networkManager.makeRequest( - "/api/v1/profile/verify-otp", - method: 'POST', - (json) => VerifyOtpResponseModel.fromJson(json), - data: data, - ); - - return result; -} + final result = await _networkManager.makeRequest( + "/api/v1/profile/verify-otp", + method: 'POST', + (json) => VerifyOtpResponseModel.fromJson(json), + data: data, + ); + return result; + } } diff --git a/lib/data/services/model/reset_password_response/reset_password_response.dart b/lib/data/services/model/reset_password_response/reset_password_response.dart new file mode 100644 index 0000000..d913abd --- /dev/null +++ b/lib/data/services/model/reset_password_response/reset_password_response.dart @@ -0,0 +1,21 @@ +// ignore_for_file: invalid_annotation_target + +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../error/error_model.dart'; + +part 'reset_password_response.freezed.dart'; +part 'reset_password_response.g.dart'; + +@freezed +abstract class ResetPasswordResponse with _$ResetPasswordResponse { + @JsonSerializable(explicitToJson: true) + const factory ResetPasswordResponse({ + required bool? success, + required String? message, + required ErrorModel? error, + }) = _ResetPasswordResponse; + + factory ResetPasswordResponse.fromJson(Map json) => + _$ResetPasswordResponseFromJson(json); +} diff --git a/lib/data/services/model/reset_password_response/reset_password_response.freezed.dart b/lib/data/services/model/reset_password_response/reset_password_response.freezed.dart new file mode 100644 index 0000000..3d6ec75 --- /dev/null +++ b/lib/data/services/model/reset_password_response/reset_password_response.freezed.dart @@ -0,0 +1,307 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'reset_password_response.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$ResetPasswordResponse { + + bool? get success; String? get message; ErrorModel? get error; +/// Create a copy of ResetPasswordResponse +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ResetPasswordResponseCopyWith get copyWith => _$ResetPasswordResponseCopyWithImpl(this as ResetPasswordResponse, _$identity); + + /// Serializes this ResetPasswordResponse to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ResetPasswordResponse&&(identical(other.success, success) || other.success == success)&&(identical(other.message, message) || other.message == message)&&(identical(other.error, error) || other.error == error)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,success,message,error); + +@override +String toString() { + return 'ResetPasswordResponse(success: $success, message: $message, error: $error)'; +} + + +} + +/// @nodoc +abstract mixin class $ResetPasswordResponseCopyWith<$Res> { + factory $ResetPasswordResponseCopyWith(ResetPasswordResponse value, $Res Function(ResetPasswordResponse) _then) = _$ResetPasswordResponseCopyWithImpl; +@useResult +$Res call({ + bool? success, String? message, ErrorModel? error +}); + + +$ErrorModelCopyWith<$Res>? get error; + +} +/// @nodoc +class _$ResetPasswordResponseCopyWithImpl<$Res> + implements $ResetPasswordResponseCopyWith<$Res> { + _$ResetPasswordResponseCopyWithImpl(this._self, this._then); + + final ResetPasswordResponse _self; + final $Res Function(ResetPasswordResponse) _then; + +/// Create a copy of ResetPasswordResponse +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? success = freezed,Object? message = freezed,Object? error = freezed,}) { + return _then(_self.copyWith( +success: freezed == success ? _self.success : success // ignore: cast_nullable_to_non_nullable +as bool?,message: freezed == message ? _self.message : message // ignore: cast_nullable_to_non_nullable +as String?,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable +as ErrorModel?, + )); +} +/// Create a copy of ResetPasswordResponse +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$ErrorModelCopyWith<$Res>? get error { + if (_self.error == null) { + return null; + } + + return $ErrorModelCopyWith<$Res>(_self.error!, (value) { + return _then(_self.copyWith(error: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [ResetPasswordResponse]. +extension ResetPasswordResponsePatterns on ResetPasswordResponse { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ResetPasswordResponse value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ResetPasswordResponse() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ResetPasswordResponse value) $default,){ +final _that = this; +switch (_that) { +case _ResetPasswordResponse(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ResetPasswordResponse value)? $default,){ +final _that = this; +switch (_that) { +case _ResetPasswordResponse() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( bool? success, String? message, ErrorModel? error)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ResetPasswordResponse() when $default != null: +return $default(_that.success,_that.message,_that.error);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( bool? success, String? message, ErrorModel? error) $default,) {final _that = this; +switch (_that) { +case _ResetPasswordResponse(): +return $default(_that.success,_that.message,_that.error);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( bool? success, String? message, ErrorModel? error)? $default,) {final _that = this; +switch (_that) { +case _ResetPasswordResponse() when $default != null: +return $default(_that.success,_that.message,_that.error);case _: + return null; + +} +} + +} + +/// @nodoc + +@JsonSerializable(explicitToJson: true) +class _ResetPasswordResponse implements ResetPasswordResponse { + const _ResetPasswordResponse({required this.success, required this.message, required this.error}); + factory _ResetPasswordResponse.fromJson(Map json) => _$ResetPasswordResponseFromJson(json); + +@override final bool? success; +@override final String? message; +@override final ErrorModel? error; + +/// Create a copy of ResetPasswordResponse +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ResetPasswordResponseCopyWith<_ResetPasswordResponse> get copyWith => __$ResetPasswordResponseCopyWithImpl<_ResetPasswordResponse>(this, _$identity); + +@override +Map toJson() { + return _$ResetPasswordResponseToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ResetPasswordResponse&&(identical(other.success, success) || other.success == success)&&(identical(other.message, message) || other.message == message)&&(identical(other.error, error) || other.error == error)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,success,message,error); + +@override +String toString() { + return 'ResetPasswordResponse(success: $success, message: $message, error: $error)'; +} + + +} + +/// @nodoc +abstract mixin class _$ResetPasswordResponseCopyWith<$Res> implements $ResetPasswordResponseCopyWith<$Res> { + factory _$ResetPasswordResponseCopyWith(_ResetPasswordResponse value, $Res Function(_ResetPasswordResponse) _then) = __$ResetPasswordResponseCopyWithImpl; +@override @useResult +$Res call({ + bool? success, String? message, ErrorModel? error +}); + + +@override $ErrorModelCopyWith<$Res>? get error; + +} +/// @nodoc +class __$ResetPasswordResponseCopyWithImpl<$Res> + implements _$ResetPasswordResponseCopyWith<$Res> { + __$ResetPasswordResponseCopyWithImpl(this._self, this._then); + + final _ResetPasswordResponse _self; + final $Res Function(_ResetPasswordResponse) _then; + +/// Create a copy of ResetPasswordResponse +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? success = freezed,Object? message = freezed,Object? error = freezed,}) { + return _then(_ResetPasswordResponse( +success: freezed == success ? _self.success : success // ignore: cast_nullable_to_non_nullable +as bool?,message: freezed == message ? _self.message : message // ignore: cast_nullable_to_non_nullable +as String?,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable +as ErrorModel?, + )); +} + +/// Create a copy of ResetPasswordResponse +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$ErrorModelCopyWith<$Res>? get error { + if (_self.error == null) { + return null; + } + + return $ErrorModelCopyWith<$Res>(_self.error!, (value) { + return _then(_self.copyWith(error: value)); + }); +} +} + +// dart format on diff --git a/lib/data/services/model/reset_password_response/reset_password_response.g.dart b/lib/data/services/model/reset_password_response/reset_password_response.g.dart new file mode 100644 index 0000000..a8aec38 --- /dev/null +++ b/lib/data/services/model/reset_password_response/reset_password_response.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'reset_password_response.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_ResetPasswordResponse _$ResetPasswordResponseFromJson( + Map json, +) => _ResetPasswordResponse( + success: json['success'] as bool?, + message: json['message'] as String?, + error: + json['error'] == null + ? null + : ErrorModel.fromJson(json['error'] as Map), +); + +Map _$ResetPasswordResponseToJson( + _ResetPasswordResponse instance, +) => { + 'success': instance.success, + 'message': instance.message, + 'error': instance.error?.toJson(), +}; diff --git a/lib/view_models/auth_view_model.dart b/lib/view_models/auth_view_model.dart index 3b49dee..43454b3 100644 --- a/lib/view_models/auth_view_model.dart +++ b/lib/view_models/auth_view_model.dart @@ -8,6 +8,7 @@ import '../core/utils/result.dart'; import '../data/repositories/auth_repository.dart'; import '../data/services/model/login_response/login_response_model.dart'; import '../data/services/model/logout_response/logout_response.dart'; +import '../data/services/model/reset_password_response/reset_password_response.dart'; class AuthViewModel with ChangeNotifier { final AuthRepository _authRepository; @@ -44,13 +45,17 @@ class AuthViewModel with ChangeNotifier { // Auth state bool _isLoading = false; + bool _isResetPasswordLoading = false; String? _errorMessage; + String? _resetPasswordErrorMessage; Customer? _loggedInUser; String? successMessage; String? _forgotPasswordMessage; bool get isLoading => _isLoading; + bool get isResetPasswordLoading => _isResetPasswordLoading; String? get errorMessage => _errorMessage; + String? get resetPasswordErrorMessage => _resetPasswordErrorMessage; Customer? get loggedInUser => _loggedInUser; String? get forgotPasswordMessage => _forgotPasswordMessage; String? resetToken; @@ -217,6 +222,28 @@ class AuthViewModel with ChangeNotifier { notifyListeners(); } + Future resetPassword() async { + _isResetPasswordLoading = true; + _resetPasswordErrorMessage = null; + notifyListeners(); + + final result = await _authRepository.resetPassword( + newPassword: newPasswordController.text.trim(), + token: '', + ); + switch (result) { + case Ok(): + break; + case Error(): + _resetPasswordErrorMessage = result.error.toString(); + break; + } + _isResetPasswordLoading = false; + notifyListeners(); + _resetPasswordErrorMessage = null; + notifyListeners(); + } + @override void dispose() { usernameController.dispose(); 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 4598a41..d22c3ba 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 @@ -4,17 +4,45 @@ import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/views/forget_password_create/strings/forgot_password_create_strings.dart'; -import 'package:tm_app/views/forget_password_otp.dart/pages/m_forget_password_otp.dart_page.dart'; import '../../../core/constants/assets.dart'; import '../../../core/theme/colors.dart'; +import '../../../core/utils/app_toast.dart'; import '../../../view_models/auth_view_model.dart'; import '../../login/widgets/login_textfield.dart'; import '../../shared/base_button.dart'; -class DesktopForgotPasswordCreatePage extends StatelessWidget { +class DesktopForgotPasswordCreatePage extends StatefulWidget { const DesktopForgotPasswordCreatePage({super.key}); + @override + State createState() => + _DesktopForgotPasswordCreatePageState(); +} + +class _DesktopForgotPasswordCreatePageState + extends State { + late final AuthViewModel viewModel; + + @override + void initState() { + viewModel = context.read(); + viewModel.addListener(_viewModelListener); + super.initState(); + } + + @override + void dispose() { + viewModel.removeListener(_viewModelListener); + super.dispose(); + } + + void _viewModelListener() { + if (viewModel.resetPasswordErrorMessage != null) { + AppToast.error(context, viewModel.resetPasswordErrorMessage!); + } + } + @override Widget build(BuildContext context) { return Scaffold( @@ -117,7 +145,7 @@ class DesktopForgotPasswordCreatePage extends StatelessWidget { ), ), SizedBox(height: 80.0.h()), - viewModel.isLoading + viewModel.isResetPasswordLoading ? const BaseButton( isEnabled: false, text: ForgotPasswordCreateStrings.resetLink, @@ -129,14 +157,7 @@ class DesktopForgotPasswordCreatePage extends StatelessWidget { onPressed: viewModel.isNewPasswordValid ? () async { - Navigator.push( - context, - MaterialPageRoute( - builder: - (context) => - const MobileForgotPasswordOtpPage(), - ), - ); + viewModel.resetPassword(); } : null, ), 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 8495e3b..339c6a2 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 @@ -4,17 +4,45 @@ import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/views/forget_password_create/strings/forgot_password_create_strings.dart'; -import 'package:tm_app/views/forget_password_otp.dart/pages/m_forget_password_otp.dart_page.dart'; import '../../../core/constants/assets.dart'; import '../../../core/theme/colors.dart'; +import '../../../core/utils/app_toast.dart'; import '../../../view_models/auth_view_model.dart'; import '../../login/widgets/login_textfield.dart'; import '../../shared/base_button.dart'; -class MobileForgotPasswordCreatePage extends StatelessWidget { +class MobileForgotPasswordCreatePage extends StatefulWidget { const MobileForgotPasswordCreatePage({super.key}); + @override + State createState() => + _MobileForgotPasswordCreatePageState(); +} + +class _MobileForgotPasswordCreatePageState + extends State { + late final AuthViewModel viewModel; + + @override + void initState() { + viewModel = context.read(); + viewModel.addListener(_viewModelListener); + super.initState(); + } + + @override + void dispose() { + viewModel.removeListener(_viewModelListener); + super.dispose(); + } + + void _viewModelListener() { + if (viewModel.resetPasswordErrorMessage != null) { + AppToast.error(context, viewModel.resetPasswordErrorMessage!); + } + } + @override Widget build(BuildContext context) { return Scaffold( @@ -114,7 +142,7 @@ class MobileForgotPasswordCreatePage extends StatelessWidget { ), ), SizedBox(height: 80.0.h()), - viewModel.isLoading + viewModel.isResetPasswordLoading ? const BaseButton( isEnabled: false, text: ForgotPasswordCreateStrings.resetLink, @@ -126,14 +154,7 @@ class MobileForgotPasswordCreatePage extends StatelessWidget { onPressed: viewModel.isNewPasswordValid ? () async { - Navigator.push( - context, - MaterialPageRoute( - builder: - (context) => - const MobileForgotPasswordOtpPage(), - ), - ); + viewModel.resetPassword(); } : null, ), 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 597c595..fbb189f 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 @@ -4,17 +4,45 @@ import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/views/forget_password_create/strings/forgot_password_create_strings.dart'; -import 'package:tm_app/views/forget_password_otp.dart/pages/m_forget_password_otp.dart_page.dart'; import '../../../core/constants/assets.dart'; import '../../../core/theme/colors.dart'; +import '../../../core/utils/app_toast.dart'; import '../../../view_models/auth_view_model.dart'; import '../../login/widgets/login_textfield.dart'; import '../../shared/base_button.dart'; -class TabletForgotPasswordCreatePage extends StatelessWidget { +class TabletForgotPasswordCreatePage extends StatefulWidget { const TabletForgotPasswordCreatePage({super.key}); + @override + State createState() => + _TabletForgotPasswordCreatePageState(); +} + +class _TabletForgotPasswordCreatePageState + extends State { + late final AuthViewModel viewModel; + + @override + void initState() { + viewModel = context.read(); + viewModel.addListener(_viewModelListener); + super.initState(); + } + + @override + void dispose() { + viewModel.removeListener(_viewModelListener); + super.dispose(); + } + + void _viewModelListener() { + if (viewModel.resetPasswordErrorMessage != null) { + AppToast.error(context, viewModel.resetPasswordErrorMessage!); + } + } + @override Widget build(BuildContext context) { return Scaffold( @@ -117,7 +145,7 @@ class TabletForgotPasswordCreatePage extends StatelessWidget { ), ), SizedBox(height: 80.0.h()), - viewModel.isLoading + viewModel.isResetPasswordLoading ? const BaseButton( isEnabled: false, text: ForgotPasswordCreateStrings.resetLink, @@ -129,14 +157,7 @@ class TabletForgotPasswordCreatePage extends StatelessWidget { onPressed: viewModel.isNewPasswordValid ? () async { - Navigator.push( - context, - MaterialPageRoute( - builder: - (context) => - const MobileForgotPasswordOtpPage(), - ), - ); + viewModel.resetPassword(); } : null, ),