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 ConsumerStatefulWidget { const SignupPage({super.key}); @override ConsumerState createState() => _SignupPageState(); } class _SignupPageState extends ConsumerState { final _formKey = GlobalKey(); final _emailController = TextEditingController(); final _passwordController = TextEditingController(); final _confirmPasswordController = TextEditingController(); bool _isLoading = false; String? _errorMessage; bool _agreedToTerms = false; @override void dispose() { _emailController.dispose(); _passwordController.dispose(); _confirmPasswordController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Sign Up'), 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( 'Create Account', style: Theme.of(context).textTheme.headlineMedium?.copyWith( fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.onSurface, ), textAlign: TextAlign.center, ), const SizedBox(height: 8), Text( 'Start tracking your household inventory today', 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, confirmPassword: _confirmPasswordController.text, onConfirmPasswordChanged: (value) { setState(() { _errorMessage = null; _confirmPasswordController.text = value; }); }, confirmPasswordError: null, onSubmit: _handleSignup, submitButtonText: 'Create Account', isLoading: _isLoading, autofocusEmail: true, formWideError: _errorMessage, onErrorDismissed: () { setState(() { _errorMessage = null; }); }, ), // Terms and conditions checkbox _buildTermsCheckbox(), const SizedBox(height: 24), // Sign in link Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'Already have an account? ', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), TextButton( onPressed: _handleSignIn, child: Text( 'Sign In', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.primary, fontWeight: FontWeight.w600, ), ), ), ], ), const SizedBox(height: 12), // Forgot password link Align( alignment: Alignment.center, child: TextButton( onPressed: _handleForgotPassword, child: Text( 'Forgot Password?', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.primary, fontWeight: FontWeight.w500, ), ), ), ), ], ), ), ), ), ), ); } Widget _buildTermsCheckbox() { return Row( children: [ Checkbox( value: _agreedToTerms, onChanged: (value) { setState(() { _agreedToTerms = value ?? false; }); }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), Expanded( child: GestureDetector( onTap: () { setState(() { _agreedToTerms = !_agreedToTerms; }); }, child: Text.rich( TextSpan( text: 'I agree to the ', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onSurface, ), children: [ WidgetSpan( child: GestureDetector( onTap: _handleTermsOfService, child: Text( 'Terms of Service', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.primary, decoration: TextDecoration.underline, ), ), ), ), const TextSpan(text: ' and '), WidgetSpan( child: GestureDetector( onTap: _handlePrivacyPolicy, child: Text( 'Privacy Policy', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.primary, decoration: TextDecoration.underline, ), ), ), ), ], ), ), ), ), ], ); } Future _handleSignup() async { // Clear previous errors setState(() { _errorMessage = null; }); // Get form values final email = _emailController.text.trim(); final password = _passwordController.text; final confirmPassword = _confirmPasswordController.text; // Validate terms agreement if (!_agreedToTerms) { 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; } // Show loading state setState(() { _isLoading = true; }); try { // TODO: Implement actual registration logic // For now, simulate API call await Future.delayed(const Duration(seconds: 2)); // Simulate successful registration if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Account created successfully! Please sign in.'), duration: Duration(seconds: 3), backgroundColor: Colors.green, ), ); // Navigate to login page context.go('/login'); } } catch (e) { // Handle registration errors setState(() { _isLoading = false; // TODO: Implement proper error handling // For now, show generic errors based on common scenarios if (e.toString().contains('email')) { _emailError = 'Email is already registered'; } else { _passwordError = 'Registration failed. Please try again.'; } }); } } void _handleTermsOfService() { // TODO: Navigate to Terms of Service screen or open web view showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text('Terms of Service'), content: const SingleChildScrollView( child: Text( 'Terms of Service placeholder...\n\n' 'This is a placeholder for the actual Terms of Service document. ' 'In a real application, this would contain detailed information about:\n' '- User responsibilities\n' '- Data usage policies\n' '- Service limitations\n' '- Account termination policies\n' '- Legal disclaimers\n\n' 'Please review the actual terms before agreeing.', ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Close'), ), ], ); }, ); } void _handlePrivacyPolicy() { // TODO: Navigate to Privacy Policy screen or open web view showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text('Privacy Policy'), content: const SingleChildScrollView( child: Text( 'Privacy Policy placeholder...\n\n' 'This is a placeholder for the actual Privacy Policy document. ' 'In a real application, this would contain detailed information about:\n' '- What data we collect\n' '- How we use your data\n' '- Data sharing practices\n' '- Data security measures\n' '- User rights and choices\n\n' 'Please review the actual policy before agreeing.', ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Close'), ), ], ); }, ); } void _handleSignIn() { // Navigate to sign in page context.go('/login'); } void _handleForgotPassword() { // Navigate to password reset page context.go('/reset-password'); } }