docs(01-06): complete password update plan
Tasks completed: 2/2 - Create password update page with validation - Update auth repository for password reset Files created: - lib/features/authentication/presentation/pages/update_password_page.dart - lib/core/utils/password_validator.dart - lib/features/authentication/presentation/pages/reset_password_confirm_page.dart SUMMARY: .planning/phases/01-authentication/01-06-SUMMARY.md
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../providers/auth_provider.dart';
|
||||
import '../../../../core/errors/auth_exceptions.dart';
|
||||
|
||||
/// Password reset confirmation page
|
||||
///
|
||||
/// This page handles the initial verification of password reset tokens
|
||||
/// before redirecting to the password update page.
|
||||
class ResetPasswordConfirmPage extends ConsumerStatefulWidget {
|
||||
const ResetPasswordConfirmPage({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<ResetPasswordConfirmPage> createState() => _ResetPasswordConfirmPageState();
|
||||
}
|
||||
|
||||
class _ResetPasswordConfirmPageState extends ConsumerState<ResetPasswordConfirmPage> {
|
||||
bool _isLoading = false;
|
||||
String? _errorMessage;
|
||||
bool _tokenValid = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_validateResetToken();
|
||||
}
|
||||
|
||||
Future<void> _validateResetToken() async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_errorMessage = null;
|
||||
});
|
||||
|
||||
try {
|
||||
// Check if we have a valid reset session by attempting to get current user
|
||||
final authProvider = ref.read(authProvider.notifier);
|
||||
await authProvider.getCurrentUser();
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
_tokenValid = true;
|
||||
});
|
||||
|
||||
// Navigate to update password page after short delay
|
||||
Future.delayed(const Duration(seconds: 2), () {
|
||||
if (mounted) {
|
||||
Navigator.of(context).pushReplacementNamed('/update-password');
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
_tokenValid = false;
|
||||
_errorMessage = _mapErrorToMessage(e);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String _mapErrorToMessage(Object error) {
|
||||
if (error is InvalidTokenException) {
|
||||
return 'This password reset link has expired or is invalid. Please request a new one.';
|
||||
} else if (error is SessionExpiredException) {
|
||||
return 'Password reset session expired. Please request a new reset link.';
|
||||
} else if (error is NetworkException) {
|
||||
return 'Network error. Please check your connection and try again.';
|
||||
} else if (error is AuthException) {
|
||||
return error.message;
|
||||
} else {
|
||||
return 'An unexpected error occurred. Please try again.';
|
||||
}
|
||||
}
|
||||
|
||||
void _navigateToReset() {
|
||||
Navigator.of(context).pushReplacementNamed('/reset-password');
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final colorScheme = theme.colorScheme;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Reset Password'),
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: _navigateToReset,
|
||||
tooltip: 'Close',
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const SizedBox(height: 48),
|
||||
if (_isLoading) ...[
|
||||
_buildLoadingIndicator(),
|
||||
] else if (_tokenValid) ...[
|
||||
_buildSuccessMessage(),
|
||||
] else ...[
|
||||
_buildErrorMessage(),
|
||||
const SizedBox(height: 32),
|
||||
_buildRetryButton(),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoadingIndicator() {
|
||||
return Column(
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
'Validating reset link...',
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSuccessMessage() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.check_circle_outline,
|
||||
size: 64,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Reset Link Valid',
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Redirecting you to set a new password...',
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const CircularProgressIndicator(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildErrorMessage() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.errorContainer,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.error_outline,
|
||||
size: 64,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Invalid Reset Link',
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
_errorMessage ?? 'This password reset link is not valid.',
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRetryButton() {
|
||||
return OutlinedButton(
|
||||
onPressed: _navigateToReset,
|
||||
style: OutlinedButton.styleFrom(
|
||||
minimumSize: const Size(double.infinity, 48),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
child: const Text('Request New Reset Link'),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user