reset password fix
This commit is contained in:
parent
dc8feb9c9e
commit
4a4c785304
@ -33,7 +33,7 @@ class ApiAuthRepository implements AuthRepository {
|
||||
body: requestBody,
|
||||
);
|
||||
|
||||
if (response.statusCode >= 200) {
|
||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||
return _parseUserFromResponse(
|
||||
response.body,
|
||||
fallbackUsername: username.trim(),
|
||||
@ -73,7 +73,7 @@ class ApiAuthRepository implements AuthRepository {
|
||||
body: requestBody,
|
||||
);
|
||||
|
||||
if (response.statusCode >= 200) {
|
||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||
return _parseUserFromResponse(
|
||||
response.body,
|
||||
fallbackUsername: username,
|
||||
@ -89,9 +89,14 @@ class ApiAuthRepository implements AuthRepository {
|
||||
Future<void> resetPassword({
|
||||
required String userId,
|
||||
required String password,
|
||||
required String oldPassword,
|
||||
}) async {
|
||||
final uri = Uri.parse('$baseUrl/auth/reset-password');
|
||||
final requestBody = jsonEncode({'userId': userId, 'password': password});
|
||||
final requestBody = jsonEncode({
|
||||
'userId': userId,
|
||||
'password': password,
|
||||
'password2': oldPassword,
|
||||
});
|
||||
|
||||
final response = await _client.put(
|
||||
uri,
|
||||
@ -99,7 +104,7 @@ class ApiAuthRepository implements AuthRepository {
|
||||
body: requestBody,
|
||||
);
|
||||
|
||||
if (response.statusCode >= 200) {
|
||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -119,7 +124,7 @@ class ApiAuthRepository implements AuthRepository {
|
||||
headers: {'Content-Type': 'application/json', 'x-api-key': apiSecret},
|
||||
);
|
||||
|
||||
if (response.statusCode >= 200) {
|
||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||
return _extractSuccessMessage(
|
||||
response.body,
|
||||
fallback: 'Password reset successfully',
|
||||
@ -148,7 +153,7 @@ class ApiAuthRepository implements AuthRepository {
|
||||
body: requestBody,
|
||||
);
|
||||
|
||||
if (response.statusCode >= 200) {
|
||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||
return _extractSuccessMessage(
|
||||
response.body,
|
||||
fallback: 'Password changed successfully',
|
||||
@ -169,7 +174,7 @@ class ApiAuthRepository implements AuthRepository {
|
||||
body: requestBody,
|
||||
);
|
||||
|
||||
if (response.statusCode >= 200) {
|
||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -49,12 +49,16 @@ class MockAuthRepository implements AuthRepository {
|
||||
Future<void> resetPassword({
|
||||
required String userId,
|
||||
required String password,
|
||||
required String oldPassword,
|
||||
}) async {
|
||||
await Future<void>.delayed(const Duration(milliseconds: 500));
|
||||
|
||||
if (userId.trim().isEmpty) {
|
||||
throw Exception('User ID is required.');
|
||||
}
|
||||
if (oldPassword.isEmpty) {
|
||||
throw Exception('Old password is required.');
|
||||
}
|
||||
if (password.length < 8) {
|
||||
throw Exception('Password must be at least 8 characters.');
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ abstract class AuthRepository {
|
||||
Future<void> resetPassword({
|
||||
required String userId,
|
||||
required String password,
|
||||
required String oldPassword,
|
||||
});
|
||||
|
||||
Future<String> resetCashierPassword({required String username});
|
||||
|
||||
@ -31,7 +31,9 @@ class LoginPage extends ConsumerWidget {
|
||||
}
|
||||
|
||||
TextInput.finishAutofillContext();
|
||||
ref.read(loginViewModelProvider.notifier).login(
|
||||
ref
|
||||
.read(loginViewModelProvider.notifier)
|
||||
.login(
|
||||
username: usernameController.text,
|
||||
password: passwordController.text,
|
||||
);
|
||||
@ -51,6 +53,7 @@ class LoginPage extends ConsumerWidget {
|
||||
switch (action) {
|
||||
case LoginRequiredAction.passwordUpdateRequired:
|
||||
final userId = (next.requiredUserId ?? '').trim();
|
||||
final oldPassword = next.requiredOldPassword ?? '';
|
||||
if (userId.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
@ -61,6 +64,18 @@ class LoginPage extends ConsumerWidget {
|
||||
ref.read(loginViewModelProvider.notifier).clearRequirement();
|
||||
return;
|
||||
}
|
||||
if (oldPassword.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text(
|
||||
'Password update required (missing login password)',
|
||||
),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
ref.read(loginViewModelProvider.notifier).clearRequirement();
|
||||
return;
|
||||
}
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
@ -70,7 +85,10 @@ class LoginPage extends ConsumerWidget {
|
||||
);
|
||||
ref.read(loginViewModelProvider.notifier).clearRequirement();
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(builder: (_) => ResetPasswordPage(userId: userId)),
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) =>
|
||||
ResetPasswordPage(userId: userId, oldPassword: oldPassword),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
@ -199,11 +217,11 @@ class LoginPage extends ConsumerWidget {
|
||||
filled: true,
|
||||
fillColor: colorScheme.surfaceContainerHighest
|
||||
.withOpacity(
|
||||
Theme.of(context).brightness ==
|
||||
Brightness.dark
|
||||
? 0.55
|
||||
: 0.9,
|
||||
),
|
||||
Theme.of(context).brightness ==
|
||||
Brightness.dark
|
||||
? 0.55
|
||||
: 0.9,
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
@ -240,11 +258,11 @@ class LoginPage extends ConsumerWidget {
|
||||
filled: true,
|
||||
fillColor: colorScheme.surfaceContainerHighest
|
||||
.withOpacity(
|
||||
Theme.of(context).brightness ==
|
||||
Brightness.dark
|
||||
? 0.55
|
||||
: 0.9,
|
||||
),
|
||||
Theme.of(context).brightness ==
|
||||
Brightness.dark
|
||||
? 0.55
|
||||
: 0.9,
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
@ -254,13 +272,14 @@ class LoginPage extends ConsumerWidget {
|
||||
: 'Hide password',
|
||||
onPressed: state.isLoading
|
||||
? null
|
||||
: () => ref
|
||||
.read(
|
||||
loginObscurePasswordProvider
|
||||
.notifier,
|
||||
)
|
||||
.state =
|
||||
!obscurePassword,
|
||||
: () =>
|
||||
ref
|
||||
.read(
|
||||
loginObscurePasswordProvider
|
||||
.notifier,
|
||||
)
|
||||
.state =
|
||||
!obscurePassword,
|
||||
icon: Icon(
|
||||
obscurePassword
|
||||
? Icons.visibility_outlined
|
||||
|
||||
@ -12,6 +12,7 @@ class LoginState {
|
||||
this.user,
|
||||
this.requiredAction,
|
||||
this.requiredUserId,
|
||||
this.requiredOldPassword,
|
||||
});
|
||||
|
||||
final bool isLoading;
|
||||
@ -20,6 +21,7 @@ class LoginState {
|
||||
final LoginUser? user;
|
||||
final LoginRequiredAction? requiredAction;
|
||||
final String? requiredUserId;
|
||||
final String? requiredOldPassword;
|
||||
|
||||
LoginState copyWith({
|
||||
bool? isLoading,
|
||||
@ -28,6 +30,7 @@ class LoginState {
|
||||
LoginUser? user,
|
||||
LoginRequiredAction? requiredAction,
|
||||
String? requiredUserId,
|
||||
String? requiredOldPassword,
|
||||
bool clearError = false,
|
||||
bool clearSuccess = false,
|
||||
bool clearUser = false,
|
||||
@ -40,13 +43,20 @@ class LoginState {
|
||||
? null
|
||||
: successMessage ?? this.successMessage,
|
||||
user: clearUser ? null : user ?? this.user,
|
||||
requiredAction: clearRequired ? null : requiredAction ?? this.requiredAction,
|
||||
requiredUserId: clearRequired ? null : requiredUserId ?? this.requiredUserId,
|
||||
requiredAction: clearRequired
|
||||
? null
|
||||
: requiredAction ?? this.requiredAction,
|
||||
requiredUserId: clearRequired
|
||||
? null
|
||||
: requiredUserId ?? this.requiredUserId,
|
||||
requiredOldPassword: clearRequired
|
||||
? null
|
||||
: requiredOldPassword ?? this.requiredOldPassword,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'LoginState(isLoading: $isLoading, errorMessage: $errorMessage, successMessage: $successMessage, user: $user, requiredAction: $requiredAction, requiredUserId: $requiredUserId)';
|
||||
return 'LoginState(isLoading: $isLoading, errorMessage: $errorMessage, successMessage: $successMessage, user: $user, requiredAction: $requiredAction, requiredUserId: $requiredUserId, requiredOldPassword: $requiredOldPassword)';
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,6 +129,7 @@ class LoginViewModel extends StateNotifier<LoginState> {
|
||||
isLoading: false,
|
||||
requiredAction: LoginRequiredAction.passwordUpdateRequired,
|
||||
requiredUserId: error.userId,
|
||||
requiredOldPassword: password,
|
||||
clearError: true,
|
||||
clearSuccess: true,
|
||||
);
|
||||
|
||||
@ -6,9 +6,14 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class ResetPasswordPage extends ConsumerWidget {
|
||||
const ResetPasswordPage({required this.userId, super.key});
|
||||
const ResetPasswordPage({
|
||||
required this.userId,
|
||||
required this.oldPassword,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String userId;
|
||||
final String oldPassword;
|
||||
|
||||
bool _isStrongPassword(String value) {
|
||||
return value.length >= 8 &&
|
||||
@ -52,7 +57,11 @@ class ResetPasswordPage extends ConsumerWidget {
|
||||
|
||||
ref
|
||||
.read(resetPasswordViewModelProvider.notifier)
|
||||
.resetPassword(userId: userId, password: password);
|
||||
.resetPassword(
|
||||
userId: userId,
|
||||
password: password,
|
||||
oldPassword: oldPassword,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -64,19 +73,13 @@ class ResetPasswordPage extends ConsumerWidget {
|
||||
final message = next.errorMessage;
|
||||
if (message != null && message != previous?.errorMessage) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(message),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
SnackBar(content: Text(message), behavior: SnackBarBehavior.floating),
|
||||
);
|
||||
}
|
||||
final success = next.successMessage;
|
||||
if (success != null && success != previous?.successMessage) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(success),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
SnackBar(content: Text(success), behavior: SnackBarBehavior.floating),
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
@ -101,14 +104,18 @@ class ResetPasswordPage extends ConsumerWidget {
|
||||
child: SafeArea(
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 20, vertical: 24),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 24,
|
||||
),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 440),
|
||||
child: Card(
|
||||
elevation: 0,
|
||||
color: colorScheme.surface.withOpacity(
|
||||
Theme.of(context).brightness == Brightness.dark ? 0.75 : 0.92,
|
||||
Theme.of(context).brightness == Brightness.dark
|
||||
? 0.75
|
||||
: 0.92,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
@ -159,7 +166,9 @@ class ResetPasswordPage extends ConsumerWidget {
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
?.copyWith(fontWeight: FontWeight.w800),
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Your account requires a password change.',
|
||||
@ -167,7 +176,8 @@ class ResetPasswordPage extends ConsumerWidget {
|
||||
.textTheme
|
||||
.bodyMedium
|
||||
?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
color:
|
||||
colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -182,8 +192,9 @@ class ResetPasswordPage extends ConsumerWidget {
|
||||
enabled: !state.isLoading,
|
||||
textInputAction: TextInputAction.next,
|
||||
obscureText: obscure,
|
||||
onFieldSubmitted: (_) =>
|
||||
FocusScope.of(context).requestFocus(confirmFocusNode),
|
||||
onFieldSubmitted: (_) => FocusScope.of(
|
||||
context,
|
||||
).requestFocus(confirmFocusNode),
|
||||
validator: (value) {
|
||||
final text = (value ?? '').trim();
|
||||
if (text.isEmpty) {
|
||||
@ -201,24 +212,28 @@ class ResetPasswordPage extends ConsumerWidget {
|
||||
filled: true,
|
||||
fillColor: colorScheme.surfaceContainerHighest
|
||||
.withOpacity(
|
||||
Theme.of(context).brightness == Brightness.dark
|
||||
? 0.55
|
||||
: 0.9,
|
||||
),
|
||||
Theme.of(context).brightness ==
|
||||
Brightness.dark
|
||||
? 0.55
|
||||
: 0.9,
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
suffixIcon: IconButton(
|
||||
tooltip:
|
||||
obscure ? 'Show password' : 'Hide password',
|
||||
tooltip: obscure
|
||||
? 'Show password'
|
||||
: 'Hide password',
|
||||
onPressed: state.isLoading
|
||||
? null
|
||||
: () => ref
|
||||
.read(
|
||||
resetObscurePasswordProvider.notifier,
|
||||
)
|
||||
.state =
|
||||
!obscure,
|
||||
: () =>
|
||||
ref
|
||||
.read(
|
||||
resetObscurePasswordProvider
|
||||
.notifier,
|
||||
)
|
||||
.state =
|
||||
!obscure,
|
||||
icon: Icon(
|
||||
obscure
|
||||
? Icons.visibility_outlined
|
||||
@ -255,10 +270,11 @@ class ResetPasswordPage extends ConsumerWidget {
|
||||
filled: true,
|
||||
fillColor: colorScheme.surfaceContainerHighest
|
||||
.withOpacity(
|
||||
Theme.of(context).brightness == Brightness.dark
|
||||
? 0.55
|
||||
: 0.9,
|
||||
),
|
||||
Theme.of(context).brightness ==
|
||||
Brightness.dark
|
||||
? 0.55
|
||||
: 0.9,
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
@ -268,13 +284,14 @@ class ResetPasswordPage extends ConsumerWidget {
|
||||
: 'Hide password',
|
||||
onPressed: state.isLoading
|
||||
? null
|
||||
: () => ref
|
||||
.read(
|
||||
resetObscureConfirmPasswordProvider
|
||||
.notifier,
|
||||
)
|
||||
.state =
|
||||
!obscureConfirm,
|
||||
: () =>
|
||||
ref
|
||||
.read(
|
||||
resetObscureConfirmPasswordProvider
|
||||
.notifier,
|
||||
)
|
||||
.state =
|
||||
!obscureConfirm,
|
||||
icon: Icon(
|
||||
obscureConfirm
|
||||
? Icons.visibility_outlined
|
||||
@ -288,25 +305,33 @@ class ResetPasswordPage extends ConsumerWidget {
|
||||
onPressed: state.isLoading
|
||||
? null
|
||||
: () => _submit(
|
||||
context: context,
|
||||
ref: ref,
|
||||
formKey: formKey,
|
||||
passwordController: passwordController,
|
||||
confirmController: confirmController,
|
||||
isLoading: state.isLoading,
|
||||
),
|
||||
context: context,
|
||||
ref: ref,
|
||||
formKey: formKey,
|
||||
passwordController: passwordController,
|
||||
confirmController: confirmController,
|
||||
isLoading: state.isLoading,
|
||||
),
|
||||
icon: state.isLoading
|
||||
? const SizedBox(
|
||||
height: 18,
|
||||
width: 18,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
)
|
||||
: const Icon(Icons.check),
|
||||
label: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12,
|
||||
),
|
||||
child: Text(
|
||||
state.isLoading ? 'Updating...' : 'Reset password',
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
state.isLoading
|
||||
? 'Updating...'
|
||||
: 'Reset password',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -314,7 +339,8 @@ class ResetPasswordPage extends ConsumerWidget {
|
||||
Text(
|
||||
'Tip: use a unique password you don’t use elsewhere.',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
style: Theme.of(context).textTheme.bodySmall
|
||||
?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
|
||||
@ -4,20 +4,23 @@ import 'package:e_receipt_mobile/presentation/reset_password/reset_password_stat
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
final resetPasswordViewModelProvider =
|
||||
StateNotifierProvider.autoDispose<ResetPasswordViewModel, ResetPasswordState>(
|
||||
(ref) {
|
||||
return ResetPasswordViewModel(ref.watch(authRepositoryProvider));
|
||||
},
|
||||
);
|
||||
StateNotifierProvider.autoDispose<
|
||||
ResetPasswordViewModel,
|
||||
ResetPasswordState
|
||||
>((ref) {
|
||||
return ResetPasswordViewModel(ref.watch(authRepositoryProvider));
|
||||
});
|
||||
|
||||
class ResetPasswordViewModel extends StateNotifier<ResetPasswordState> {
|
||||
ResetPasswordViewModel(this._authRepository) : super(const ResetPasswordState());
|
||||
ResetPasswordViewModel(this._authRepository)
|
||||
: super(const ResetPasswordState());
|
||||
|
||||
final AuthRepository _authRepository;
|
||||
|
||||
Future<void> resetPassword({
|
||||
required String userId,
|
||||
required String password,
|
||||
required String oldPassword,
|
||||
}) async {
|
||||
if (userId.trim().isEmpty) {
|
||||
state = state.copyWith(
|
||||
@ -27,6 +30,14 @@ class ResetPasswordViewModel extends StateNotifier<ResetPasswordState> {
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (oldPassword.isEmpty) {
|
||||
state = state.copyWith(
|
||||
isLoading: false,
|
||||
errorMessage: 'Missing original login password',
|
||||
clearSuccess: true,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
state = state.copyWith(
|
||||
isLoading: true,
|
||||
@ -35,7 +46,11 @@ class ResetPasswordViewModel extends StateNotifier<ResetPasswordState> {
|
||||
);
|
||||
|
||||
try {
|
||||
await _authRepository.resetPassword(userId: userId.trim(), password: password);
|
||||
await _authRepository.resetPassword(
|
||||
userId: userId.trim(),
|
||||
password: password,
|
||||
oldPassword: oldPassword,
|
||||
);
|
||||
state = state.copyWith(
|
||||
isLoading: false,
|
||||
successMessage: 'Password updated',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user