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 ConsumerStatefulWidget { const LoginPage({super.key}); @override ConsumerState createState() => _LoginPageState(); } class _LoginPageState extends ConsumerState { final _formKey = GlobalKey(); final _emailController = TextEditingController(); final _passwordController = TextEditingController(); bool _isLoading = false; String? _errorMessage; @override void dispose() { _emailController.dispose(); _passwordController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Sign In'), centerTitle: true, elevation: 0, backgroundColor: Colors.transparent, foregroundColor: Theme.of(context).colorScheme.onSurface, ), body: SafeArea( child: Padding( padding: const EdgeInsets.all(24.0), child: Center( child: SingleChildScrollView( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Logo or app title could go here Icon( Icons.inventory_2_outlined, size: 80, color: Theme.of(context).colorScheme.primary, ), const SizedBox(height: 32), Text( 'Welcome Back', style: Theme.of(context).textTheme.headlineMedium?.copyWith( fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.onSurface, ), textAlign: TextAlign.center, ), const SizedBox(height: 8), Text( 'Sign in to track your household inventory', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), textAlign: TextAlign.center, ), const SizedBox(height: 32), // Authentication form AuthForm( formKey: _formKey, email: _emailController.text, onEmailChanged: (value) { setState(() { _errorMessage = null; _emailController.text = value; }); }, emailError: null, password: _passwordController.text, onPasswordChanged: (value) { setState(() { _errorMessage = null; _passwordController.text = value; }); }, passwordError: null, onSubmit: _handleLogin, submitButtonText: 'Sign In', isLoading: _isLoading, autofocusEmail: true, ), 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( onPressed: _handleForgotPassword, child: Text( 'Forgot Password?', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.primary, fontWeight: FontWeight.w500, ), ), ), ), const SizedBox(height: 24), // Sign up link Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'Don\'t have an account? ', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), TextButton( onPressed: _handleSignUp, child: Text( 'Sign Up', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.primary, fontWeight: FontWeight.w600, ), ), ), ], ), ], ), ), ), ), ), ); } Future _handleLogin() async { // Clear previous errors setState(() { _errorMessage = null; }); // Validate form if (!_formKey.currentState!.validate()) { 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 { final authProvider = ref.read(authProvider.notifier); await authProvider.signIn(email, password); if (mounted) { // Navigate to home or dashboard context.go('/home'); } } 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 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'); } void _handleSignUp() { // Navigate to sign up page context.go('/signup'); } }