diff --git a/lib/features/authentication/presentation/pages/login_page.dart b/lib/features/authentication/presentation/pages/login_page.dart index 1458229..9428790 100644 --- a/lib/features/authentication/presentation/pages/login_page.dart +++ b/lib/features/authentication/presentation/pages/login_page.dart @@ -1,24 +1,26 @@ 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'; /// Login screen with email/password authentication -class LoginPage extends StatefulWidget { +class LoginPage extends ConsumerStatefulWidget { const LoginPage({super.key}); @override - State createState() => _LoginPageState(); + ConsumerState createState() => _LoginPageState(); } -class _LoginPageState extends State { +class _LoginPageState extends ConsumerState { final _formKey = GlobalKey(); final _emailController = TextEditingController(); final _passwordController = TextEditingController(); bool _isLoading = false; - String? _emailError; - String? _passwordError; + String? _errorMessage; @override void dispose() { @@ -79,27 +81,61 @@ class _LoginPageState 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, onSubmit: _handleLogin, submitButtonText: 'Sign In', isLoading: _isLoading, autofocusEmail: true, - ), - const SizedBox(height: 16), - - // Forgot password link +), + 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), + ], + + // Forgot password link Align( alignment: Alignment.centerRight, child: TextButton( @@ -149,8 +185,7 @@ class _LoginPageState extends State { Future _handleLogin() async { // Clear previous errors setState(() { - _emailError = null; - _passwordError = null; + _errorMessage = null; }); // Validate form @@ -158,33 +193,72 @@ class _LoginPageState extends State { return; } + // Check for empty fields + final email = _emailController.text.trim(); + final password = _passwordController.text; + + if (email.isEmpty || password.isEmpty) { + setState(() { + _errorMessage = 'Please enter both email and password'; + }); + return; + } + // Show loading state setState(() { _isLoading = true; }); try { - // TODO: Implement actual authentication logic - // For now, simulate API call - await Future.delayed(const Duration(seconds: 2)); + final authProvider = ref.read(authProvider.notifier); + await authProvider.signIn(email, password); - // Simulate successful login if (mounted) { // Navigate to home or dashboard context.go('/home'); } } catch (e) { - // Handle authentication errors - setState(() { - _isLoading = false; + if (mounted) { + setState(() { + _isLoading = false; + _errorMessage = _mapErrorToUserFriendlyMessage(e); + }); - // TODO: Implement proper error handling - // For now, show generic error - _passwordError = 'Invalid email or password'; - }); + // Announce error for accessibility + _announceErrorForAccessibility(_errorMessage!); + } } } + /// Maps authentication exceptions to user-friendly error messages + String _mapErrorToUserFriendlyMessage(Object error) { + if (error is InvalidCredentialsException) { + return 'Invalid password. Please check your password and try again.'; + } else if (error is UserNotFoundException) { + return 'Account not found. Please check your email address or sign up for a new account.'; + } else if (error is NetworkException) { + return 'Network connection failed. Please check your internet connection and try again.'; + } else if (error is SessionExpiredException) { + return 'Your session has expired. Please sign in again.'; + } else if (error is EmailNotVerifiedException) { + return 'Please verify your email address before signing in. Check your inbox for verification email.'; + } else if (error is AuthDisabledException) { + return 'Authentication is currently disabled. Please try again later.'; + } else if (error is TooManyRequestsException) { + return 'Too many login attempts. Please wait a moment and try again.'; + } else if (error is AuthException) { + return error.message; + } else { + return 'An unexpected error occurred. Please try again.'; + } + } + + /// Announces error message for screen readers + void _announceErrorForAccessibility(String errorMessage) { + // Use Semantics to announce the error for screen readers + SemanticsService.announce(errorMessage, TextDirection.ltr); + } + void _handleForgotPassword() { // Navigate to password reset page context.go('/reset-password');