import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; /// Reusable authentication form with email and password fields class AuthForm extends StatefulWidget { final GlobalKey formKey; final String email; final ValueChanged? onEmailChanged; final String? emailError; final String password; final ValueChanged? onPasswordChanged; final String? passwordError; final VoidCallback? onSubmit; final String submitButtonText; final bool isLoading; final bool showPasswordToggle; final String? confirmPassword; final ValueChanged? onConfirmPasswordChanged; final String? confirmPasswordError; final bool autofocusEmail; final String? emailLabel; final String? passwordLabel; final String? confirmPasswordLabel; const AuthForm({ super.key, required this.formKey, this.email = '', this.onEmailChanged, this.emailError, this.password = '', this.onPasswordChanged, this.passwordError, this.onSubmit, this.submitButtonText = 'Submit', this.isLoading = false, this.showPasswordToggle = true, this.confirmPassword, this.onConfirmPasswordChanged, this.confirmPasswordError, this.autofocusEmail = true, this.emailLabel = 'Email', this.passwordLabel = 'Password', this.confirmPasswordLabel = 'Confirm Password', }); @override State createState() => _AuthFormState(); } class _AuthFormState extends State { bool _obscurePassword = true; bool _obscureConfirmPassword = true; @override Widget build(BuildContext context) { return Form( key: widget.formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _buildEmailField(), const SizedBox(height: 16), _buildPasswordField(), if (widget.confirmPassword != null) ...[ const SizedBox(height: 16), _buildConfirmPasswordField(), ], const SizedBox(height: 24), _buildSubmitButton(), ], ), ); } Widget _buildEmailField() { return TextFormField( initialValue: widget.email, onChanged: widget.onEmailChanged, autofocus: widget.autofocusEmail, keyboardType: TextInputType.emailAddress, textInputAction: TextInputAction.next, decoration: InputDecoration( labelText: widget.emailLabel, hintText: 'Enter your email address', prefixIcon: const Icon(Icons.email_outlined), errorText: widget.emailError, border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: widget.emailError != null ? Theme.of(context).colorScheme.error : Theme.of(context).colorScheme.outline, ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: widget.emailError != null ? Theme.of(context).colorScheme.error : Theme.of(context).colorScheme.primary, width: 2, ), ), ), validator: (value) { if (value == null || value.trim().isEmpty) { return 'Email is required'; } // Basic email validation regex final emailRegex = RegExp( r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', ); if (!emailRegex.hasMatch(value.trim())) { return 'Please enter a valid email address'; } return null; }, inputFormatters: [ FilteringTextInputFormatter.deny(RegExp(r'\s')), // Prevent spaces ], ); } Widget _buildPasswordField() { return TextFormField( initialValue: widget.password, onChanged: widget.onPasswordChanged, obscureText: _obscurePassword, textInputAction: widget.confirmPassword != null ? TextInputAction.next : TextInputAction.done, onFieldSubmitted: (_) { if (widget.confirmPassword != null) { // Move focus to confirm password field FocusScope.of(context).nextFocus(); } else { // Submit form widget.onSubmit?.call(); } }, decoration: InputDecoration( labelText: widget.passwordLabel, hintText: 'Enter your password', prefixIcon: const Icon(Icons.lock_outline), suffixIcon: widget.showPasswordToggle ? IconButton( icon: Icon( _obscurePassword ? Icons.visibility_off : Icons.visibility, ), onPressed: () { setState(() { _obscurePassword = !_obscurePassword; }); }, tooltip: _obscurePassword ? 'Show password' : 'Hide password', ) : null, errorText: widget.passwordError, border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: widget.passwordError != null ? Theme.of(context).colorScheme.error : Theme.of(context).colorScheme.outline, ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: widget.passwordError != null ? Theme.of(context).colorScheme.error : Theme.of(context).colorScheme.primary, width: 2, ), ), ), validator: (value) { if (value == null || value.isEmpty) { return 'Password is required'; } if (value.length < 6) { return 'Password must be at least 6 characters'; } // Check for at least one letter and one number final hasLetter = RegExp(r'[a-zA-Z]').hasMatch(value); final hasNumber = RegExp(r'\d').hasMatch(value); if (!hasLetter || !hasNumber) { return 'Password must contain both letters and numbers'; } return null; }, ); } Widget _buildConfirmPasswordField() { return TextFormField( initialValue: widget.confirmPassword, onChanged: widget.onConfirmPasswordChanged, obscureText: _obscureConfirmPassword, textInputAction: TextInputAction.done, onFieldSubmitted: (_) => widget.onSubmit?.call(), decoration: InputDecoration( labelText: widget.confirmPasswordLabel, hintText: 'Confirm your password', prefixIcon: const Icon(Icons.lock_outline), suffixIcon: widget.showPasswordToggle ? IconButton( icon: Icon( _obscureConfirmPassword ? Icons.visibility_off : Icons.visibility, ), onPressed: () { setState(() { _obscureConfirmPassword = !_obscureConfirmPassword; }); }, tooltip: _obscureConfirmPassword ? 'Show password' : 'Hide password', ) : null, errorText: widget.confirmPasswordError, border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: widget.confirmPasswordError != null ? Theme.of(context).colorScheme.error : Theme.of(context).colorScheme.outline, ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: widget.confirmPasswordError != null ? Theme.of(context).colorScheme.error : Theme.of(context).colorScheme.primary, width: 2, ), ), ), validator: (value) { if (value == null || value.isEmpty) { return 'Please confirm your password'; } if (value != widget.password) { return 'Passwords do not match'; } return null; }, ); } Widget _buildSubmitButton() { return ElevatedButton( onPressed: widget.isLoading ? null : widget.onSubmit, style: ElevatedButton.styleFrom( backgroundColor: Theme.of(context).colorScheme.primary, foregroundColor: Theme.of(context).colorScheme.onPrimary, disabledBackgroundColor: Theme.of(context).colorScheme.primary.withOpacity(0.12), disabledForegroundColor: Theme.of(context).colorScheme.onPrimary.withOpacity(0.38), elevation: 2, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), minimumSize: const Size(double.infinity, 48), ), child: widget.isLoading ? SizedBox( height: 20, width: 20, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation( Theme.of(context).colorScheme.onPrimary, ), ), ) : Text( widget.submitButtonText, style: Theme.of(context).textTheme.labelLarge?.copyWith( fontWeight: FontWeight.w600, ), ), ); } }