import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../../core/error_mapper.dart'; import '../../../core/responsive/responsive_layout.dart'; import '../../../core/theme/app_typography.dart'; import '../../../core/theme/theme_extensions.dart'; import '../../../services/auth/auth_providers.dart'; import '../../../services/clubs/club_models.dart'; import '../../../services/clubs/club_providers.dart'; import '../../../services/clubs/club_service.dart'; import 'widgets/slug_input_field.dart'; /// Screen allowing authenticated riders to create a new Club. /// Features live slug availability verification and atomic creation. class CreateClubScreen extends ConsumerStatefulWidget { const CreateClubScreen({super.key}); @override ConsumerState createState() => _CreateClubScreenState(); } class _CreateClubScreenState extends ConsumerState { final _formKey = GlobalKey(); final _nameController = TextEditingController(); final _slugController = TextEditingController(); final _descController = TextEditingController(); final _regionController = TextEditingController(); final _focusController = TextEditingController(); ClubVisibility _visibility = ClubVisibility.public; ClubAdmissionPolicy _admissionPolicy = ClubAdmissionPolicy.request; bool _isSlugValidAndAvailable = false; bool _isSubmitting = false; String? _submissionError; @override void dispose() { _nameController.dispose(); _slugController.dispose(); _descController.dispose(); _regionController.dispose(); _focusController.dispose(); super.dispose(); } Future _submitForm() async { if (_isSubmitting) return; if (!_formKey.currentState!.validate()) return; if (!_isSlugValidAndAvailable) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Please choose a valid and available club handle.'), behavior: SnackBarBehavior.floating, ), ); return; } setState(() { _isSubmitting = true; _submissionError = null; }); try { final user = ref.read(currentUserProvider); if (user == null || user.isAnonymous) { throw const ClubOperationException( 'You must be signed in with a registered account to create a club.', code: 'unauthenticated', ); } final service = ref.read(clubServiceProvider); final createdClub = await service.createClub( name: _nameController.text.trim(), slug: _slugController.text.trim(), description: _descController.text.trim(), visibility: _visibility, admissionPolicy: _admissionPolicy, homeRegion: _regionController.text.trim().isEmpty ? null : _regionController.text.trim(), motorcycleFocus: _focusController.text.trim().isEmpty ? null : _focusController.text.trim(), callerUid: user.uid, ); // Invalidate relevant providers to reflect new club ref.invalidate(myClubsProvider); ref.invalidate(publicClubsProvider); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Club "${createdClub.name}" created successfully!'), behavior: SnackBarBehavior.floating, ), ); context.pushReplacement('/social/clubs/${createdClub.id}'); } on ClubOperationException catch (e) { if (!mounted) return; setState(() { _isSubmitting = false; if (e.code == 'slug-taken') { _submissionError = 'This handle was just taken by another club. Please pick a different handle.'; } else { _submissionError = e.message; } }); } catch (e) { if (!mounted) return; setState(() { _isSubmitting = false; _submissionError = 'An unexpected error occurred while creating the club: ${ErrorMapper.map(e)}'; }); } } @override Widget build(BuildContext context) { final colors = context.motorColors; final theme = Theme.of(context); final user = ref.watch(currentUserProvider); final isAuthenticated = user != null && !user.isAnonymous; if (!isAuthenticated) { return Scaffold( appBar: AppBar(title: const Text('Create Club')), body: Center( child: Padding( padding: const EdgeInsets.all(24.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.shield_outlined, size: 48, color: colors.cyberCyan), const SizedBox(height: 16), const Text( 'Account Required', style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 18, fontWeight: FontWeight.w700, ), ), const SizedBox(height: 8), Text( 'Sign in or create a MotorSignal account to join and manage riding clubs.', textAlign: TextAlign.center, style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 13, color: theme.colorScheme.onSurfaceVariant, ), ), const SizedBox(height: 20), FilledButton.icon( onPressed: () => context.push('/settings/account'), style: FilledButton.styleFrom( backgroundColor: colors.cyberCyan, foregroundColor: Colors.black, ), icon: const Icon(Icons.login, size: 18), label: const Text('Sign In / Register'), ), ], ), ), ), ); } return Scaffold( appBar: AppBar( title: const Text( 'Create Club', style: TextStyle( fontFamily: AppTypography.fontHeadline, fontWeight: FontWeight.w700, ), ), ), body: SafeArea( child: ResponsiveLayout( builder: (context, responsive) { return Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 700), child: Form( key: _formKey, child: ListView( padding: const EdgeInsets.all(18.0), children: [ // Header card explaining Club identity Container( padding: const EdgeInsets.all(14.0), decoration: BoxDecoration( color: colors.surfaceContainer, borderRadius: BorderRadius.circular(12), border: Border.all( color: colors.outlineVariant, width: 0.8, ), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon( Icons.shield_outlined, color: colors.cyberCyan, size: 24, ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Found a Motorcycle Club', style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 14, fontWeight: FontWeight.w700, ), ), const SizedBox(height: 3), Text( 'As the founder, you will be designated the Club Owner with administrative authority over admissions, group rides, and club settings.', style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 12, color: theme.colorScheme.onSurfaceVariant, height: 1.3, ), ), ], ), ), ], ), ), const SizedBox(height: 20), // Submission error alert if present if (_submissionError != null) ...[ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: colors.raceRed.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(10), border: Border.all( color: colors.raceRed.withValues(alpha: 0.4), ), ), child: Row( children: [ Icon( Icons.error_outline, color: colors.raceRed, size: 20, ), const SizedBox(width: 8), Expanded( child: Text( _submissionError!, style: TextStyle( fontFamily: AppTypography.fontBody, fontSize: 12.5, color: colors.raceRed, ), ), ), ], ), ), const SizedBox(height: 16), ], // Club Name TextFormField( controller: _nameController, enabled: !_isSubmitting, textCapitalization: TextCapitalization.words, decoration: InputDecoration( labelText: 'Club Name *', hintText: 'e.g. Ridge Runners MC', filled: true, fillColor: colors.surfaceContainer, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: colors.outlineVariant, width: 0.8, ), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: colors.outlineVariant, width: 0.8, ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: colors.cyberCyan, width: 1.5, ), ), ), validator: (val) { final clean = val?.trim() ?? ''; if (clean.length < 3) { return 'Name must be at least 3 characters'; } if (clean.length > 50) { return 'Name cannot exceed 50 characters'; } return null; }, ), const SizedBox(height: 18), // Slug / Handle with live debounce & availability checking SlugInputField( controller: _slugController, enabled: !_isSubmitting, onValidationChanged: (isValid) { setState(() { _isSlugValidAndAvailable = isValid; }); }, ), const SizedBox(height: 18), // Description TextFormField( controller: _descController, enabled: !_isSubmitting, maxLines: 3, maxLength: 500, textCapitalization: TextCapitalization.sentences, decoration: InputDecoration( labelText: 'Description (Optional)', hintText: 'Share your club mission, riding style, or club guidelines...', filled: true, fillColor: colors.surfaceContainer, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: colors.outlineVariant, width: 0.8, ), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: colors.outlineVariant, width: 0.8, ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: colors.cyberCyan, width: 1.5, ), ), ), ), const SizedBox(height: 14), // Give optional discovery fields enough room for a clear // label and example on narrow phone layouts. _buildFieldLabel( 'Home base', 'Where this club primarily rides or organizes from.', colors, ), const SizedBox(height: 8), TextFormField( controller: _regionController, enabled: !_isSubmitting, textCapitalization: TextCapitalization.words, decoration: InputDecoration( labelText: 'Home region (optional)', hintText: 'e.g. North Georgia, NorCal, Phoenix Metro', prefixIcon: const Icon(Icons.location_on_outlined), filled: true, fillColor: colors.surfaceContainer, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: colors.outlineVariant, width: 0.8, ), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: colors.outlineVariant, width: 0.8, ), ), ), ), const SizedBox(height: 18), _buildFieldLabel( 'Rider community', 'Name the motorcycle types or riding style this club is for.', colors, ), const SizedBox(height: 8), TextFormField( controller: _focusController, enabled: !_isSubmitting, textCapitalization: TextCapitalization.words, decoration: InputDecoration( labelText: 'Motorcycle focus (optional)', hintText: 'e.g. Sport bikes, ADV, Cruisers, Mixed rides', prefixIcon: const Icon(Icons.two_wheeler_outlined), filled: true, fillColor: colors.surfaceContainer, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: colors.outlineVariant, width: 0.8, ), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: colors.outlineVariant, width: 0.8, ), ), ), ), const SizedBox(height: 24), // Visibility Selection Text( 'Club Visibility', style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 13, fontWeight: FontWeight.w700, color: colors.cyberCyan, ), ), const SizedBox(height: 8), _buildVisibilitySelector(colors, theme), const SizedBox(height: 20), // Admission Policy Selection Text( 'Admission Policy', style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 13, fontWeight: FontWeight.w700, color: colors.cyberCyan, ), ), const SizedBox(height: 8), _buildAdmissionSelector(colors, theme), const SizedBox(height: 32), // Submit Button SizedBox( height: 50, child: FilledButton( key: const ValueKey('create_club_submit_button'), onPressed: (_isSubmitting || !_isSlugValidAndAvailable) ? null : _submitForm, style: FilledButton.styleFrom( backgroundColor: colors.cyberCyan, foregroundColor: Colors.black, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: _isSubmitting ? const SizedBox( width: 24, height: 24, child: CircularProgressIndicator( strokeWidth: 2.5, valueColor: AlwaysStoppedAnimation( Colors.black, ), ), ) : const Text( 'Create Club', style: TextStyle( fontFamily: AppTypography.fontHeadline, fontSize: 15, fontWeight: FontWeight.w800, letterSpacing: 0.3, ), ), ), ), const SizedBox(height: 32), ], ), ), ), ); }, ), ), ); } Widget _buildVisibilitySelector( MotorSignalThemeColors colors, ThemeData theme, ) { return DropdownButtonFormField( initialValue: _visibility, decoration: _choiceDecoration('Who can find this club', colors), dropdownColor: colors.surfaceContainerHigh, style: const TextStyle(color: Colors.white), items: ClubVisibility.values .map( (value) => DropdownMenuItem(value: value, child: Text(value.label)), ) .toList(), onChanged: _isSubmitting ? null : (value) { if (value != null) setState(() => _visibility = value); }, ); } Widget _buildAdmissionSelector( MotorSignalThemeColors colors, ThemeData theme, ) { return DropdownButtonFormField( initialValue: _admissionPolicy, decoration: _choiceDecoration('How riders join', colors), dropdownColor: colors.surfaceContainerHigh, style: const TextStyle(color: Colors.white), items: ClubAdmissionPolicy.values .map( (value) => DropdownMenuItem(value: value, child: Text(value.label)), ) .toList(), onChanged: _isSubmitting ? null : (value) { if (value != null) setState(() => _admissionPolicy = value); }, ); } Widget _buildFieldLabel( String title, String description, MotorSignalThemeColors colors, ) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: TextStyle( color: colors.cyberCyan, fontWeight: FontWeight.w700, fontSize: 13, ), ), const SizedBox(height: 3), Text( description, style: TextStyle( color: Theme.of(context).colorScheme.onSurfaceVariant, fontSize: 12, height: 1.25, ), ), ], ); } InputDecoration _choiceDecoration( String label, MotorSignalThemeColors colors, ) { return InputDecoration( labelText: label, prefixIcon: const Icon(Icons.tune), filled: true, fillColor: colors.surfaceContainer, border: OutlineInputBorder(borderRadius: BorderRadius.circular(10)), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: BorderSide(color: colors.outlineVariant), ), ); } }