import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../widgets/auth_button.dart'; import '../providers/auth_provider.dart'; import '../../../../core/errors/auth_exceptions.dart'; import '../../../../core/utils/password_validator.dart'; import '../../../../core/router/app_router.dart'; /// Password update page for handling password reset from email links /// /// This page handles the password reset flow when users click on reset links /// from their email. It validates the reset token and allows users to set /// a new password. class UpdatePasswordPage extends ConsumerStatefulWidget { const UpdatePasswordPage({super.key}); @override ConsumerState createState() => _UpdatePasswordPageState(); } class _UpdatePasswordPageState extends ConsumerState { final _formKey = GlobalKey(); final _newPasswordController = TextEditingController(); final _confirmPasswordController = TextEditingController(); bool _isLoading = false; bool _showPassword = false; bool _showConfirmPassword = false; String? _errorMessage; bool _passwordUpdated = false; String? _resetToken; String? _resetEmail; @override void initState() { super.initState(); _extractResetParameters(); } @override void dispose() { _newPasswordController.dispose(); _confirmPasswordController.dispose(); super.dispose(); } /// Extract reset token and email from URL parameters for deep linking void _extractResetParameters() { final resetData = AppRouter.handlePasswordResetDeepLink(context); _resetToken = resetData['token']; _resetEmail = resetData['email']; if (_resetToken != null && _resetEmail != null) { print('Reset parameters extracted for email: $_resetEmail'); } } Future _handlePasswordUpdate() async { if (!_formKey.currentState!.validate()) return; setState(() { _isLoading = true; _errorMessage = null; }); try { final authProvider = ref.read(authProvider.notifier); // Use reset token and email if available (from deep linking) if (_resetToken != null && _resetEmail != null) { await authProvider.updatePasswordWithToken( _resetToken!, _resetEmail!, _newPasswordController.text.trim(), ); } else { // Fallback to regular password update await authProvider.updatePasswordFromReset( _newPasswordController.text.trim(), ); } if (mounted) { setState(() { _isLoading = false; _passwordUpdated = true; }); } } catch (e) { if (mounted) { setState(() { _isLoading = 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 WeakPasswordException) { return 'Password is too weak. Please choose a stronger password.'; } 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 _navigateToLogin() { Navigator.of(context).pushReplacementNamed('/login'); } bool _isPasswordValid() { final password = _newPasswordController.text; return password.length >= 8 && password.contains(RegExp(r'[A-Z]')) && password.contains(RegExp(r'[a-z]')) && password.contains(RegExp(r'[0-9]')); } String? _validatePassword(String? value) { if (value == null || value.isEmpty) { return 'Please enter a new password'; } if (value.length < 8) { return 'Password must be at least 8 characters'; } if (!value.contains(RegExp(r'[A-Z]'))) { return 'Password must contain at least one uppercase letter'; } if (!value.contains(RegExp(r'[a-z]'))) { return 'Password must contain at least one lowercase letter'; } if (!value.contains(RegExp(r'[0-9]'))) { return 'Password must contain at least one number'; } return null; } String? _validateConfirmPassword(String? value) { if (value == null || value.isEmpty) { return 'Please confirm your new password'; } if (value != _newPasswordController.text) { return 'Passwords do not match'; } return null; } @override Widget build(BuildContext context) { final theme = Theme.of(context); final colorScheme = theme.colorScheme; return Scaffold( appBar: AppBar( title: const Text('Update Password'), backgroundColor: Colors.transparent, elevation: 0, leading: IconButton( icon: const Icon(Icons.close), onPressed: _navigateToLogin, tooltip: 'Cancel', ), ), body: SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.all(24.0), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const SizedBox(height: 32), if (!_passwordUpdated) ...[ _buildHeader(), const SizedBox(height: 32), _buildInstructions(), const SizedBox(height: 32), _buildPasswordForm(), const SizedBox(height: 24), _buildSubmitButton(), if (_errorMessage != null) ...[ const SizedBox(height: 16), _buildErrorMessage(), ], ] else ...[ _buildSuccessMessage(), const SizedBox(height: 32), _buildLoginButton(), ], ], ), ), ), ); } Widget _buildHeader() { return Column( children: [ Icon( Icons.lock_outline, size: 64, color: Theme.of(context).colorScheme.primary, ), const SizedBox(height: 16), Text( 'Set New Password', style: Theme.of(context).textTheme.headlineMedium?.copyWith( fontWeight: FontWeight.bold, ), ), ], ); } Widget _buildInstructions() { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Theme.of(context).colorScheme.surfaceVariant, borderRadius: BorderRadius.circular(12), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( Icons.info_outline, size: 20, color: Theme.of(context).colorScheme.onSurfaceVariant, ), const SizedBox(width: 8), Expanded( child: Text( 'Create a strong password for your account security.', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), ), ], ), ], ), ); } Widget _buildPasswordForm() { return Form( key: _formKey, child: Column( children: [ TextFormField( controller: _newPasswordController, obscureText: !_showPassword, enabled: !_isLoading, decoration: InputDecoration( labelText: 'New Password', hintText: 'Enter your new password', prefixIcon: const Icon(Icons.lock_outline), suffixIcon: IconButton( icon: Icon( _showPassword ? Icons.visibility : Icons.visibility_off, ), onPressed: () { setState(() { _showPassword = !_showPassword; }); }, ), ), validator: _validatePassword, autovalidateMode: AutovalidateMode.onUserInteraction, ), const SizedBox(height: 16), TextFormField( controller: _confirmPasswordController, obscureText: !_showConfirmPassword, enabled: !_isLoading, decoration: InputDecoration( labelText: 'Confirm Password', hintText: 'Confirm your new password', prefixIcon: const Icon(Icons.lock_outline), suffixIcon: IconButton( icon: Icon( _showConfirmPassword ? Icons.visibility : Icons.visibility_off, ), onPressed: () { setState(() { _showConfirmPassword = !_showConfirmPassword; }); }, ), ), validator: _validateConfirmPassword, autovalidateMode: AutovalidateMode.onUserInteraction, ), const SizedBox(height: 16), _buildPasswordStrengthIndicator(), ], ), ); } Widget _buildPasswordStrengthIndicator() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( 'Password Strength:', style: Theme.of(context).textTheme.bodySmall?.copyWith( fontWeight: FontWeight.w500, ), ), const SizedBox(width: 8), Text( _getPasswordStrengthText(), style: Theme.of(context).textTheme.bodySmall?.copyWith( color: _getPasswordStrengthColor(), fontWeight: FontWeight.bold, ), ), ], ), const SizedBox(height: 8), LinearProgressIndicator( value: _getPasswordStrength(), backgroundColor: Theme.of(context).colorScheme.surfaceVariant, valueColor: AlwaysStoppedAnimation(_getPasswordStrengthColor()), ), const SizedBox(height: 8), Text( 'Password must contain:\n• At least 8 characters\n• Uppercase and lowercase letters\n• At least one number', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), ], ); } double _getPasswordStrength() { final password = _newPasswordController.text; double strength = 0.0; if (password.length >= 8) strength += 0.25; if (password.contains(RegExp(r'[A-Z]'))) strength += 0.25; if (password.contains(RegExp(r'[a-z]'))) strength += 0.25; if (password.contains(RegExp(r'[0-9]'))) strength += 0.25; return strength; } String _getPasswordStrengthText() { final strength = _getPasswordStrength(); if (strength <= 0.25) return 'Weak'; if (strength <= 0.5) return 'Fair'; if (strength <= 0.75) return 'Good'; return 'Strong'; } Color _getPasswordStrengthColor() { final strength = _getPasswordStrength(); if (strength <= 0.25) return Colors.red; if (strength <= 0.5) return Colors.orange; if (strength <= 0.75) return Colors.yellow.shade700; return Colors.green; } Widget _buildSubmitButton() { return AuthButton( text: 'Update Password', onPressed: _isPasswordValid() ? _handlePasswordUpdate : null, isLoading: _isLoading, icon: const Icon(Icons.lock_reset), ); } Widget _buildErrorMessage() { return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Theme.of(context).colorScheme.errorContainer, borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Icon( Icons.error_outline, color: Theme.of(context).colorScheme.error, size: 20, ), const SizedBox(width: 8), Expanded( child: Text( _errorMessage!, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.error, ), ), ), ], ), ); } Widget _buildSuccessMessage() { return Container( padding: const EdgeInsets.all(16), 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( 'Password Updated!', style: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.onPrimaryContainer, ), ), const SizedBox(height: 8), Text( 'Your password has been successfully updated. You can now sign in with your new password.', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onPrimaryContainer, ), textAlign: TextAlign.center, ), ], ), ); } Widget _buildLoginButton() { return AuthButton( text: 'Sign In', onPressed: _navigateToLogin, variant: AuthButtonVariant.primary, icon: const Icon(Icons.login), ); } }