diff --git a/lib/features/authentication/presentation/pages/login_page.dart b/lib/features/authentication/presentation/pages/login_page.dart index 9428790..97e47ef 100644 --- a/lib/features/authentication/presentation/pages/login_page.dart +++ b/lib/features/authentication/presentation/pages/login_page.dart @@ -98,42 +98,13 @@ class _LoginPageState extends ConsumerState { 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), - ], + formWideError: _errorMessage, + onErrorDismissed: () { + setState(() { + _errorMessage = null; + }); + }, + ), // Forgot password link Align( diff --git a/lib/features/authentication/presentation/pages/signup_page.dart b/lib/features/authentication/presentation/pages/signup_page.dart index 23d4b6e..eebe420 100644 --- a/lib/features/authentication/presentation/pages/signup_page.dart +++ b/lib/features/authentication/presentation/pages/signup_page.dart @@ -109,42 +109,13 @@ class _SignupPageState extends ConsumerState { submitButtonText: 'Create Account', 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), - ], + formWideError: _errorMessage, + onErrorDismissed: () { + setState(() { + _errorMessage = null; + }); + }, + ), // Terms and conditions checkbox _buildTermsCheckbox(), diff --git a/lib/features/authentication/presentation/widgets/password_reset_form.dart b/lib/features/authentication/presentation/widgets/password_reset_form.dart index 596b42c..bd6469b 100644 --- a/lib/features/authentication/presentation/widgets/password_reset_form.dart +++ b/lib/features/authentication/presentation/widgets/password_reset_form.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter/semantics.dart'; import 'auth_button.dart'; /// Reusable password reset form with email validation and submission @@ -37,6 +38,7 @@ class PasswordResetForm extends StatefulWidget { class _PasswordResetFormState extends State { String? _emailError; + String? _lastAnnouncedError; @override void initState() { @@ -70,7 +72,16 @@ class _PasswordResetFormState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ + // Form-wide error display + if (widget.errorMessage != null) ...[ + _buildFormWideError(), + const SizedBox(height: 16), + ], _buildEmailField(), + if (_emailError != null) ...[ + const SizedBox(height: 8), + _buildFieldError(_emailError!), + ], if (widget.helperText != null) ...[ const SizedBox(height: 8), _buildHelperText(), @@ -146,12 +157,8 @@ class _PasswordResetFormState extends State { return null; }, onChanged: (value) { - // Clear error when user starts typing - if (_emailError != null) { - setState(() { - _emailError = null; - }); - } + // Clear errors when user starts typing + _clearErrorsOnUserInput(); // Call external callback if provided if (widget.onEmailChanged != null) { widget.onEmailChanged!(value); @@ -192,6 +199,88 @@ class _PasswordResetFormState extends State { ); } + /// Builds form-wide error display + Widget _buildFormWideError() { + return 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( + widget.errorMessage!, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).colorScheme.onErrorContainer, + ), + ), + ), + ], + ), + ); + } + + /// Builds field-specific error display + Widget _buildFieldError(String error) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.errorContainer.withOpacity(0.5), + borderRadius: BorderRadius.circular(6), + border: Border.left( + color: Theme.of(context).colorScheme.error, + width: 3, + ), + ), + child: Row( + children: [ + Icon( + Icons.info_outline, + size: 16, + color: Theme.of(context).colorScheme.error, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + error, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context).colorScheme.error, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ); + } + + /// Clears errors when user starts typing + void _clearErrorsOnUserInput() { + bool hadError = _emailError != null || widget.errorMessage != null; + + if (hadError && _lastAnnouncedError != widget.errorMessage) { + _announceForAccessibility('Error cleared'); + _lastAnnouncedError = widget.errorMessage; + } + } + + /// Announces messages for screen readers + void _announceForAccessibility(String message) { + SemanticsService.announce(message, TextDirection.ltr); + } + Widget _buildSubmitButton() { return AuthButton( text: widget.submitButtonText,