From ef471f28b0811c65ff6e162a08282e453448243b Mon Sep 17 00:00:00 2001 From: Dani B Date: Wed, 28 Jan 2026 12:30:46 -0500 Subject: [PATCH] feat(01-08): enhance signup page with comprehensive error handling - Connected to AuthProvider for real registration - Added specific error messages for different exception types - Implemented password mismatch validation before submission - Added error message display with proper styling - Enhanced form validation with empty field checks - Added accessibility error announcements via Semantics - Connected to real registration flow instead of simulation - Added success message with quick navigation to login - Included terms agreement validation with clear error message --- .../presentation/pages/signup_page.dart | 175 +++++++++++++++--- 1 file changed, 150 insertions(+), 25 deletions(-) diff --git a/lib/features/authentication/presentation/pages/signup_page.dart b/lib/features/authentication/presentation/pages/signup_page.dart index 10cca3f..23d4b6e 100644 --- a/lib/features/authentication/presentation/pages/signup_page.dart +++ b/lib/features/authentication/presentation/pages/signup_page.dart @@ -1,26 +1,27 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../widgets/auth_form.dart'; import '../widgets/auth_button.dart'; +import '../../../../providers/auth_provider.dart'; +import '../../../../core/errors/auth_exceptions.dart'; /// Sign up screen with user registration -class SignupPage extends StatefulWidget { +class SignupPage extends ConsumerStatefulWidget { const SignupPage({super.key}); @override - State createState() => _SignupPageState(); + ConsumerState createState() => _SignupPageState(); } -class _SignupPageState extends State { +class _SignupPageState extends ConsumerState { final _formKey = GlobalKey(); final _emailController = TextEditingController(); final _passwordController = TextEditingController(); final _confirmPasswordController = TextEditingController(); bool _isLoading = false; - String? _emailError; - String? _passwordError; - String? _confirmPasswordError; + String? _errorMessage; bool _agreedToTerms = false; @override @@ -83,35 +84,69 @@ class _SignupPageState extends State { email: _emailController.text, onEmailChanged: (value) { setState(() { - _emailError = null; + _errorMessage = null; _emailController.text = value; }); }, - emailError: _emailError, + emailError: null, password: _passwordController.text, onPasswordChanged: (value) { setState(() { - _passwordError = null; + _errorMessage = null; _passwordController.text = value; }); }, - passwordError: _passwordError, + passwordError: null, confirmPassword: _confirmPasswordController.text, onConfirmPasswordChanged: (value) { setState(() { - _confirmPasswordError = null; + _errorMessage = null; _confirmPasswordController.text = value; }); }, - confirmPasswordError: _confirmPasswordError, + confirmPasswordError: null, onSubmit: _handleSignup, submitButtonText: 'Create Account', isLoading: _isLoading, autofocusEmail: true, - ), - const SizedBox(height: 16), - - // Terms and conditions checkbox + ), + const SizedBox(height: 16), + + // Error message display + if (_errorMessage != null) ...[ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.errorContainer, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Theme.of(context).colorScheme.error, + width: 1, + ), + ), + child: Row( + children: [ + Icon( + Icons.error_outline, + size: 20, + color: Theme.of(context).colorScheme.error, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + _errorMessage!, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).colorScheme.onErrorContainer, + ), + ), + ), + ], + ), + ), + const SizedBox(height: 16), + ], + + // Terms and conditions checkbox _buildTermsCheckbox(), const SizedBox(height: 24), @@ -225,22 +260,112 @@ class _SignupPageState extends State { Future _handleSignup() async { // Clear previous errors setState(() { - _emailError = null; - _passwordError = null; - _confirmPasswordError = null; + _errorMessage = null; }); + // Get form values + final email = _emailController.text.trim(); + final password = _passwordController.text; + final confirmPassword = _confirmPasswordController.text; + // Validate terms agreement if (!_agreedToTerms) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Please agree to the Terms of Service and Privacy Policy'), - duration: Duration(seconds: 3), - ), - ); + setState(() { + _errorMessage = 'Please agree to Terms of Service and Privacy Policy'; + }); return; } + // Check password match before form validation + if (password != confirmPassword) { + setState(() { + _errorMessage = 'Passwords do not match. Please ensure both passwords are identical.'; + }); + return; + } + + // Validate form + if (!_formKey.currentState!.validate()) { + return; + } + + // Check for empty fields + if (email.isEmpty || password.isEmpty || confirmPassword.isEmpty) { + setState(() { + _errorMessage = 'Please fill in all required fields'; + }); + return; + } + + // Show loading state + setState(() { + _isLoading = true; + }); + + try { + final authProvider = ref.read(authProvider.notifier); + await authProvider.signUp(email, password); + + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: const Text('Account created successfully! Please sign in.'), + duration: const Duration(seconds: 3), + backgroundColor: Colors.green, + action: SnackBarAction( + label: 'Sign In', + onPressed: () => context.go('/login'), + ), + ), + ); + + // Navigate to login page after a short delay + Future.delayed(const Duration(seconds: 1), () { + if (mounted) { + context.go('/login'); + } + }); + } + } catch (e) { + if (mounted) { + setState(() { + _isLoading = false; + _errorMessage = _mapErrorToUserFriendlyMessage(e); + }); + + // Announce error for accessibility + _announceErrorForAccessibility(_errorMessage!); + } + } + } + + /// Maps authentication exceptions to user-friendly error messages + String _mapErrorToUserFriendlyMessage(Object error) { + if (error is EmailAlreadyInUseException) { + return 'This email address is already registered. Please use a different email or try signing in.'; + } else if (error is WeakPasswordException) { + return 'Password is too weak. Please use a stronger password with at least 8 characters, including uppercase, lowercase, and numbers.'; + } else if (error is InvalidCredentialsException) { + return 'Invalid email format. Please enter a valid email address.'; + } else if (error is NetworkException) { + return 'Network connection failed. Please check your internet connection and try again.'; + } else if (error is TooManyRequestsException) { + return 'Too many registration attempts. Please wait a moment and try again.'; + } else if (error is AuthDisabledException) { + return 'Account registration is currently disabled. Please try again later.'; + } else if (error is AuthException) { + return error.message; + } else { + return 'Registration failed. Please try again or contact support if the problem persists.'; + } + } + + /// Announces error message for screen readers + void _announceErrorForAccessibility(String errorMessage) { + // Use Semantics to announce error for screen readers + SemanticsService.announce(errorMessage, TextDirection.ltr); + } + // Validate form if (!_formKey.currentState!.validate()) { return;