import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../providers/auth_provider.dart'; import '../../providers/tickets_provider.dart'; import '../../widgets/responsive_body.dart'; class SignUpScreen extends ConsumerStatefulWidget { const SignUpScreen({super.key}); @override ConsumerState createState() => _SignUpScreenState(); } class _SignUpScreenState extends ConsumerState { final _formKey = GlobalKey(); final _fullNameController = TextEditingController(); final _emailController = TextEditingController(); final _passwordController = TextEditingController(); final _confirmPasswordController = TextEditingController(); final Set _selectedOfficeIds = {}; double _passwordStrength = 0.0; String _passwordStrengthLabel = 'Very weak'; Color _passwordStrengthColor = Colors.red; bool _isLoading = false; @override void initState() { super.initState(); _passwordController.addListener(_updatePasswordStrength); } @override void dispose() { _passwordController.removeListener(_updatePasswordStrength); _fullNameController.dispose(); _emailController.dispose(); _passwordController.dispose(); _confirmPasswordController.dispose(); super.dispose(); } Future _handleSignUp() async { if (!_formKey.currentState!.validate()) return; if (_selectedOfficeIds.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Select at least one office.')), ); return; } setState(() => _isLoading = true); final auth = ref.read(authControllerProvider); try { await auth.signUp( email: _emailController.text.trim(), password: _passwordController.text, fullName: _fullNameController.text.trim(), officeIds: _selectedOfficeIds.toList(), ); if (mounted) { context.go('/login'); } } on Exception catch (error) { if (mounted) { ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text('Sign up failed: $error'))); } } finally { if (mounted) { setState(() => _isLoading = false); } } } @override Widget build(BuildContext context) { final officesAsync = ref.watch(officesOnceProvider); return Scaffold( appBar: AppBar(title: const Text('Create Account')), body: ResponsiveBody( maxWidth: 480, padding: const EdgeInsets.symmetric(vertical: 24), child: Form( key: _formKey, child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: Column( children: [ Image.asset('assets/tasq_ico.png', height: 72, width: 72), const SizedBox(height: 12), Text( 'TasQ', style: Theme.of(context).textTheme.headlineSmall, ), ], ), ), const SizedBox(height: 24), TextFormField( controller: _fullNameController, decoration: const InputDecoration(labelText: 'Full name'), textInputAction: TextInputAction.next, validator: (value) { if (value == null || value.trim().isEmpty) { return 'Full name is required.'; } return null; }, ), const SizedBox(height: 12), TextFormField( controller: _emailController, decoration: const InputDecoration(labelText: 'Email'), keyboardType: TextInputType.emailAddress, textInputAction: TextInputAction.next, validator: (value) { if (value == null || value.trim().isEmpty) { return 'Email is required.'; } return null; }, ), const SizedBox(height: 12), TextFormField( controller: _passwordController, decoration: const InputDecoration(labelText: 'Password'), obscureText: true, textInputAction: TextInputAction.next, validator: (value) { if (value == null || value.isEmpty) { return 'Password is required.'; } if (value.length < 6) { return 'Use at least 6 characters.'; } return null; }, ), const SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Password strength: $_passwordStrengthLabel', style: Theme.of(context).textTheme.labelMedium, ), const SizedBox(height: 6), LinearProgressIndicator( value: _passwordStrength, minHeight: 8, borderRadius: BorderRadius.circular(8), color: _passwordStrengthColor, backgroundColor: Theme.of( context, ).colorScheme.surfaceContainerHighest, ), ], ), const SizedBox(height: 12), TextFormField( controller: _confirmPasswordController, decoration: const InputDecoration( labelText: 'Confirm password', ), obscureText: true, textInputAction: TextInputAction.done, onFieldSubmitted: (_) { if (!_isLoading) { _handleSignUp(); } }, validator: (value) { if (value == null || value.isEmpty) { return 'Confirm your password.'; } if (value != _passwordController.text) { return 'Passwords do not match.'; } return null; }, ), const SizedBox(height: 16), Text('Offices', style: Theme.of(context).textTheme.titleSmall), const SizedBox(height: 8), officesAsync.when( data: (offices) { if (offices.isEmpty) { return const Text('No offices available.'); } return Column( children: offices .map( (office) => CheckboxListTile( value: _selectedOfficeIds.contains(office.id), onChanged: _isLoading ? null : (selected) { setState(() { if (selected == true) { _selectedOfficeIds.add(office.id); } else { _selectedOfficeIds.remove(office.id); } }); }, title: Text(office.name), controlAffinity: ListTileControlAffinity.leading, contentPadding: EdgeInsets.zero, ), ) .toList(), ); }, loading: () => const LinearProgressIndicator(), error: (error, _) => Text('Failed to load offices: $error'), ), const SizedBox(height: 24), FilledButton( onPressed: _isLoading ? null : _handleSignUp, child: _isLoading ? const SizedBox( height: 18, width: 18, child: CircularProgressIndicator(strokeWidth: 2), ) : const Text('Create Account'), ), const SizedBox(height: 12), TextButton( onPressed: _isLoading ? null : () => context.go('/login'), child: const Text('Back to sign in'), ), ], ), ), ), ); } void _updatePasswordStrength() { final text = _passwordController.text; var score = 0; if (text.length >= 8) score++; if (text.length >= 12) score++; if (RegExp(r'[A-Z]').hasMatch(text)) score++; if (RegExp(r'[a-z]').hasMatch(text)) score++; if (RegExp(r'\d').hasMatch(text)) score++; if (RegExp(r'[!@#$%^&*(),.?":{}|<>\[\]\\/+=;_-]').hasMatch(text)) { score++; } final normalized = (score / 6).clamp(0.0, 1.0); final (label, color) = switch (normalized) { <= 0.2 => ('Very weak', Colors.red), <= 0.4 => ('Weak', Colors.deepOrange), <= 0.6 => ('Fair', Colors.orange), <= 0.8 => ('Strong', Colors.green), _ => ('Excellent', Colors.teal), }; setState(() { _passwordStrength = normalized; _passwordStrengthLabel = label; _passwordStrengthColor = color; }); } }